Version in base suite: 4.9.17-0+deb13u1 Base version: pdns_4.9.17-0+deb13u1 Target version: pdns_4.9.18-0+deb13u1 Base file: /srv/ftp-master.debian.org/ftp/pool/main/p/pdns/pdns_4.9.17-0+deb13u1.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/p/pdns/pdns_4.9.18-0+deb13u1.dsc configure | 20 +-- configure.ac | 2 debian/changelog | 7 + docs/calidns.1 | 2 docs/dnsbulktest.1 | 2 docs/dnsgram.1 | 2 docs/dnspcap2calidns.1 | 2 docs/dnspcap2protobuf.1 | 2 docs/dnsreplay.1 | 2 docs/dnsscan.1 | 2 docs/dnsscope.1 | 2 docs/dnstcpbench.1 | 2 docs/dnswasher.1 | 2 docs/dumresp.1 | 2 docs/ixfrdist.1 | 2 docs/ixfrdist.yml.5 | 2 docs/ixplore.1 | 2 docs/nproxy.1 | 2 docs/nsec3dig.1 | 2 docs/pdns_control.1 | 2 docs/pdns_notify.1 | 2 docs/pdns_server.1 | 2 docs/pdnsutil.1 | 2 docs/saxfr.1 | 2 docs/sdig.1 | 2 docs/zone2json.1 | 2 docs/zone2ldap.1 | 2 docs/zone2sql.1 | 2 modules/geoipbackend/geoipbackend.cc | 136 ++++++++++++-------------- modules/geoipbackend/geoipbackend.hh | 26 +++-- modules/lmdbbackend/lmdbbackend.cc | 1 modules/lmdbbackend/lmdbbackend.hh | 2 pdns/auth-main.cc | 8 + pdns/auth-secondarycommunicator.cc | 27 ++--- pdns/backends/gsql/gsqlbackend.cc | 180 ++++++++++++++++++++++++++++++++--- pdns/backends/gsql/gsqlbackend.hh | 145 ---------------------------- pdns/dnsname.cc | 32 +++++- pdns/dnsname.hh | 9 + pdns/dnsparser.cc | 15 +- pdns/dnsparser.hh | 21 +++- pdns/lua-record.cc | 84 ++++++++++++---- pdns/test-dnsname_cc.cc | 22 ++++ pdns/test-dnsparser_cc.cc | 114 ++++++++++++++++++++++ 43 files changed, 581 insertions(+), 320 deletions(-) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmplkh92rl5/pdns_4.9.17-0+deb13u1.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmplkh92rl5/pdns_4.9.18-0+deb13u1.dsc: no acceptable signature found diff -Nru pdns-4.9.17/configure pdns-4.9.18/configure --- pdns-4.9.17/configure 2026-08-05 13:25:02.000000000 +0000 +++ pdns-4.9.18/configure 2026-10-07 06:10:01.000000000 +0000 @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.71 for pdns 4.9.17. +# Generated by GNU Autoconf 2.71 for pdns 4.9.18. # # # Copyright (C) 1992-1996, 1998-2017, 2020-2021 Free Software Foundation, @@ -618,8 +618,8 @@ # Identity of this package. PACKAGE_NAME='pdns' PACKAGE_TARNAME='pdns' -PACKAGE_VERSION='4.9.17' -PACKAGE_STRING='pdns 4.9.17' +PACKAGE_VERSION='4.9.18' +PACKAGE_STRING='pdns 4.9.18' PACKAGE_BUGREPORT='' PACKAGE_URL='' @@ -1698,7 +1698,7 @@ # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures pdns 4.9.17 to adapt to many kinds of systems. +\`configure' configures pdns 4.9.18 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1769,7 +1769,7 @@ if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of pdns 4.9.17:";; + short | recursive ) echo "Configuration of pdns 4.9.18:";; esac cat <<\_ACEOF @@ -2040,7 +2040,7 @@ test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -pdns configure 4.9.17 +pdns configure 4.9.18 generated by GNU Autoconf 2.71 Copyright (C) 2021 Free Software Foundation, Inc. @@ -2529,7 +2529,7 @@ This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by pdns $as_me 4.9.17, which was +It was created by pdns $as_me 4.9.18, which was generated by GNU Autoconf 2.71. Invocation command line was $ $0$ac_configure_args_raw @@ -4027,7 +4027,7 @@ # Define the identity of the package. PACKAGE='pdns' - VERSION='4.9.17' + VERSION='4.9.18' printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h @@ -32469,7 +32469,7 @@ # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by pdns $as_me 4.9.17, which was +This file was extended by pdns $as_me 4.9.18, which was generated by GNU Autoconf 2.71. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -32537,7 +32537,7 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\ -pdns config.status 4.9.17 +pdns config.status 4.9.18 configured by $0, generated by GNU Autoconf 2.71, with options \\"\$ac_cs_config\\" diff -Nru pdns-4.9.17/configure.ac pdns-4.9.18/configure.ac --- pdns-4.9.17/configure.ac 2026-08-05 13:24:54.000000000 +0000 +++ pdns-4.9.18/configure.ac 2026-10-07 06:09:48.000000000 +0000 @@ -1,6 +1,6 @@ AC_PREREQ([2.69]) -AC_INIT([pdns], [4.9.17]) +AC_INIT([pdns], [4.9.18]) 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]) diff -Nru pdns-4.9.17/debian/changelog pdns-4.9.18/debian/changelog --- pdns-4.9.17/debian/changelog 2026-08-06 19:04:06.000000000 +0000 +++ pdns-4.9.18/debian/changelog 2026-10-27 10:56:54.000000000 +0000 @@ -1,3 +1,10 @@ +pdns (4.9.18-0+deb13u1) trixie-security; urgency=medium + + * New upstream version 4.9.18, fixing security issue + CVE-2026-73206 (Closes: #1152255) + + -- Chris Hofstaedtler Tue, 27 Oct 2026 11:56:54 +0100 + pdns (4.9.17-0+deb13u1) trixie-security; urgency=medium * New upstream version 4.9.17, fixing security issue diff -Nru pdns-4.9.17/docs/calidns.1 pdns-4.9.18/docs/calidns.1 --- pdns-4.9.17/docs/calidns.1 2026-08-05 13:26:01.000000000 +0000 +++ pdns-4.9.18/docs/calidns.1 2026-10-07 06:11:32.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "CALIDNS" "1" "Aug 05, 2026" "" "PowerDNS Authoritative Server" +.TH "CALIDNS" "1" "Oct 07, 2026" "" "PowerDNS Authoritative Server" .SH NAME calidns \- A DNS recursor testing tool .SH SYNOPSIS diff -Nru pdns-4.9.17/docs/dnsbulktest.1 pdns-4.9.18/docs/dnsbulktest.1 --- pdns-4.9.17/docs/dnsbulktest.1 2026-08-05 13:26:01.000000000 +0000 +++ pdns-4.9.18/docs/dnsbulktest.1 2026-10-07 06:11:32.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "DNSBULKTEST" "1" "Aug 05, 2026" "" "PowerDNS Authoritative Server" +.TH "DNSBULKTEST" "1" "Oct 07, 2026" "" "PowerDNS Authoritative Server" .SH NAME dnsbulktest \- A debugging tool for intermittent resolver failures .SH SYNOPSIS diff -Nru pdns-4.9.17/docs/dnsgram.1 pdns-4.9.18/docs/dnsgram.1 --- pdns-4.9.17/docs/dnsgram.1 2026-08-05 13:26:01.000000000 +0000 +++ pdns-4.9.18/docs/dnsgram.1 2026-10-07 06:11:32.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "DNSGRAM" "1" "Aug 05, 2026" "" "PowerDNS Authoritative Server" +.TH "DNSGRAM" "1" "Oct 07, 2026" "" "PowerDNS Authoritative Server" .SH NAME dnsgram \- A debugging tool for intermittent resolver failures .SH SYNOPSIS diff -Nru pdns-4.9.17/docs/dnspcap2calidns.1 pdns-4.9.18/docs/dnspcap2calidns.1 --- pdns-4.9.17/docs/dnspcap2calidns.1 2026-08-05 13:26:01.000000000 +0000 +++ pdns-4.9.18/docs/dnspcap2calidns.1 2026-10-07 06:11:32.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "DNSPCAP2CALIDNS" "1" "Aug 05, 2026" "" "PowerDNS Authoritative Server" +.TH "DNSPCAP2CALIDNS" "1" "Oct 07, 2026" "" "PowerDNS Authoritative Server" .SH NAME dnspcap2calidns \- A tool to convert PCAPs of DNS traffic to calidns input .SH SYNOPSIS diff -Nru pdns-4.9.17/docs/dnspcap2protobuf.1 pdns-4.9.18/docs/dnspcap2protobuf.1 --- pdns-4.9.17/docs/dnspcap2protobuf.1 2026-08-05 13:26:01.000000000 +0000 +++ pdns-4.9.18/docs/dnspcap2protobuf.1 2026-10-07 06:11:32.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "DNSPCAP2PROTOBUF" "1" "Aug 05, 2026" "" "PowerDNS Authoritative Server" +.TH "DNSPCAP2PROTOBUF" "1" "Oct 07, 2026" "" "PowerDNS Authoritative Server" .SH NAME dnspcap2protobuf \- A tool to convert PCAPs of DNS traffic to PowerDNS Protobuf .SH SYNOPSIS diff -Nru pdns-4.9.17/docs/dnsreplay.1 pdns-4.9.18/docs/dnsreplay.1 --- pdns-4.9.17/docs/dnsreplay.1 2026-08-05 13:26:01.000000000 +0000 +++ pdns-4.9.18/docs/dnsreplay.1 2026-10-07 06:11:32.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "DNSREPLAY" "1" "Aug 05, 2026" "" "PowerDNS Authoritative Server" +.TH "DNSREPLAY" "1" "Oct 07, 2026" "" "PowerDNS Authoritative Server" .SH NAME dnsreplay \- A PowerDNS nameserver debugging tool .SH SYNOPSIS diff -Nru pdns-4.9.17/docs/dnsscan.1 pdns-4.9.18/docs/dnsscan.1 --- pdns-4.9.17/docs/dnsscan.1 2026-08-05 13:26:01.000000000 +0000 +++ pdns-4.9.18/docs/dnsscan.1 2026-10-07 06:11:32.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "DNSSCAN" "1" "Aug 05, 2026" "" "PowerDNS Authoritative Server" +.TH "DNSSCAN" "1" "Oct 07, 2026" "" "PowerDNS Authoritative Server" .SH NAME dnsscan \- List the amount of queries per qtype in a pcap .SH SYNOPSIS diff -Nru pdns-4.9.17/docs/dnsscope.1 pdns-4.9.18/docs/dnsscope.1 --- pdns-4.9.17/docs/dnsscope.1 2026-08-05 13:26:01.000000000 +0000 +++ pdns-4.9.18/docs/dnsscope.1 2026-10-07 06:11:32.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "DNSSCOPE" "1" "Aug 05, 2026" "" "PowerDNS Authoritative Server" +.TH "DNSSCOPE" "1" "Oct 07, 2026" "" "PowerDNS Authoritative Server" .SH NAME dnsscope \- A PowerDNS nameserver debugging tool .SH SYNOPSIS diff -Nru pdns-4.9.17/docs/dnstcpbench.1 pdns-4.9.18/docs/dnstcpbench.1 --- pdns-4.9.17/docs/dnstcpbench.1 2026-08-05 13:26:01.000000000 +0000 +++ pdns-4.9.18/docs/dnstcpbench.1 2026-10-07 06:11:32.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "DNSTCPBENCH" "1" "Aug 05, 2026" "" "PowerDNS Authoritative Server" +.TH "DNSTCPBENCH" "1" "Oct 07, 2026" "" "PowerDNS Authoritative Server" .SH NAME dnstcpbench \- tool to perform TCP benchmarking of nameservers .SH SYNOPSIS diff -Nru pdns-4.9.17/docs/dnswasher.1 pdns-4.9.18/docs/dnswasher.1 --- pdns-4.9.17/docs/dnswasher.1 2026-08-05 13:26:01.000000000 +0000 +++ pdns-4.9.18/docs/dnswasher.1 2026-10-07 06:11:32.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "DNSWASHER" "1" "Aug 05, 2026" "" "PowerDNS Authoritative Server" +.TH "DNSWASHER" "1" "Oct 07, 2026" "" "PowerDNS Authoritative Server" .SH NAME dnswasher \- A PowerDNS nameserver debugging tool .SH SYNOPSIS diff -Nru pdns-4.9.17/docs/dumresp.1 pdns-4.9.18/docs/dumresp.1 --- pdns-4.9.17/docs/dumresp.1 2026-08-05 13:26:01.000000000 +0000 +++ pdns-4.9.18/docs/dumresp.1 2026-10-07 06:11:32.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "DUMRESP" "1" "Aug 05, 2026" "" "PowerDNS Authoritative Server" +.TH "DUMRESP" "1" "Oct 07, 2026" "" "PowerDNS Authoritative Server" .SH NAME dumresp \- A dumb DNS responder .SH SYNOPSIS diff -Nru pdns-4.9.17/docs/ixfrdist.1 pdns-4.9.18/docs/ixfrdist.1 --- pdns-4.9.17/docs/ixfrdist.1 2026-08-05 13:26:01.000000000 +0000 +++ pdns-4.9.18/docs/ixfrdist.1 2026-10-07 06:11:32.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "IXFRDIST" "1" "Aug 05, 2026" "" "PowerDNS Authoritative Server" +.TH "IXFRDIST" "1" "Oct 07, 2026" "" "PowerDNS Authoritative Server" .SH NAME ixfrdist \- An IXFR/AXFR-only server that re-distributes zones .SH SYNOPSIS diff -Nru pdns-4.9.17/docs/ixfrdist.yml.5 pdns-4.9.18/docs/ixfrdist.yml.5 --- pdns-4.9.17/docs/ixfrdist.yml.5 2026-08-05 13:26:01.000000000 +0000 +++ pdns-4.9.18/docs/ixfrdist.yml.5 2026-10-07 06:11:32.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "IXFRDIST.YML" "5" "Aug 05, 2026" "" "PowerDNS Authoritative Server" +.TH "IXFRDIST.YML" "5" "Oct 07, 2026" "" "PowerDNS Authoritative Server" .SH NAME ixfrdist.yml \- The ixfrdist configuration file .SH SYNOPSIS diff -Nru pdns-4.9.17/docs/ixplore.1 pdns-4.9.18/docs/ixplore.1 --- pdns-4.9.17/docs/ixplore.1 2026-08-05 13:26:01.000000000 +0000 +++ pdns-4.9.18/docs/ixplore.1 2026-10-07 06:11:32.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "IXPLORE" "1" "Aug 05, 2026" "" "PowerDNS Authoritative Server" +.TH "IXPLORE" "1" "Oct 07, 2026" "" "PowerDNS Authoritative Server" .SH NAME ixplore \- A tool that provides insights into IXFRs .SH SYNOPSIS diff -Nru pdns-4.9.17/docs/nproxy.1 pdns-4.9.18/docs/nproxy.1 --- pdns-4.9.17/docs/nproxy.1 2026-08-05 13:26:01.000000000 +0000 +++ pdns-4.9.18/docs/nproxy.1 2026-10-07 06:11:32.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "NPROXY" "1" "Aug 05, 2026" "" "PowerDNS Authoritative Server" +.TH "NPROXY" "1" "Oct 07, 2026" "" "PowerDNS Authoritative Server" .SH NAME nproxy \- DNS notification proxy .SH SYNOPSIS diff -Nru pdns-4.9.17/docs/nsec3dig.1 pdns-4.9.18/docs/nsec3dig.1 --- pdns-4.9.17/docs/nsec3dig.1 2026-08-05 13:26:01.000000000 +0000 +++ pdns-4.9.18/docs/nsec3dig.1 2026-10-07 06:11:32.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "NSEC3DIG" "1" "Aug 05, 2026" "" "PowerDNS Authoritative Server" +.TH "NSEC3DIG" "1" "Oct 07, 2026" "" "PowerDNS Authoritative Server" .SH NAME nsec3dig \- Show and validate NSEC3 proofs .SH SYNOPSIS diff -Nru pdns-4.9.17/docs/pdns_control.1 pdns-4.9.18/docs/pdns_control.1 --- pdns-4.9.17/docs/pdns_control.1 2026-08-05 13:26:01.000000000 +0000 +++ pdns-4.9.18/docs/pdns_control.1 2026-10-07 06:11:32.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "PDNS_CONTROL" "1" "Aug 05, 2026" "" "PowerDNS Authoritative Server" +.TH "PDNS_CONTROL" "1" "Oct 07, 2026" "" "PowerDNS Authoritative Server" .SH NAME pdns_control \- Control the PowerDNS nameserver .SH SYNOPSIS diff -Nru pdns-4.9.17/docs/pdns_notify.1 pdns-4.9.18/docs/pdns_notify.1 --- pdns-4.9.17/docs/pdns_notify.1 2026-08-05 13:26:01.000000000 +0000 +++ pdns-4.9.18/docs/pdns_notify.1 2026-10-07 06:11:32.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "PDNS_NOTIFY" "1" "Aug 05, 2026" "" "PowerDNS Authoritative Server" +.TH "PDNS_NOTIFY" "1" "Oct 07, 2026" "" "PowerDNS Authoritative Server" .SH NAME pdns_notify \- A simple DNS NOTIFY sender .SH SYNOPSIS diff -Nru pdns-4.9.17/docs/pdns_server.1 pdns-4.9.18/docs/pdns_server.1 --- pdns-4.9.17/docs/pdns_server.1 2026-08-05 13:26:01.000000000 +0000 +++ pdns-4.9.18/docs/pdns_server.1 2026-10-07 06:11:32.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "PDNS_SERVER" "1" "Aug 05, 2026" "" "PowerDNS Authoritative Server" +.TH "PDNS_SERVER" "1" "Oct 07, 2026" "" "PowerDNS Authoritative Server" .SH NAME pdns_server \- The PowerDNS Authoritative Nameserver .SH SYNOPSIS diff -Nru pdns-4.9.17/docs/pdnsutil.1 pdns-4.9.18/docs/pdnsutil.1 --- pdns-4.9.17/docs/pdnsutil.1 2026-08-05 13:26:01.000000000 +0000 +++ pdns-4.9.18/docs/pdnsutil.1 2026-10-07 06:11:32.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "PDNSUTIL" "1" "Aug 05, 2026" "" "PowerDNS Authoritative Server" +.TH "PDNSUTIL" "1" "Oct 07, 2026" "" "PowerDNS Authoritative Server" .SH NAME pdnsutil \- PowerDNS record and DNSSEC command and control .SH SYNOPSIS diff -Nru pdns-4.9.17/docs/saxfr.1 pdns-4.9.18/docs/saxfr.1 --- pdns-4.9.17/docs/saxfr.1 2026-08-05 13:26:01.000000000 +0000 +++ pdns-4.9.18/docs/saxfr.1 2026-10-07 06:11:32.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "SAXFR" "1" "Aug 05, 2026" "" "PowerDNS Authoritative Server" +.TH "SAXFR" "1" "Oct 07, 2026" "" "PowerDNS Authoritative Server" .SH NAME saxfr \- Perform AXFRs and show information about it .SH SYNOPSIS diff -Nru pdns-4.9.17/docs/sdig.1 pdns-4.9.18/docs/sdig.1 --- pdns-4.9.17/docs/sdig.1 2026-08-05 13:26:01.000000000 +0000 +++ pdns-4.9.18/docs/sdig.1 2026-10-07 06:11:32.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "SDIG" "1" "Aug 05, 2026" "" "PowerDNS Authoritative Server" +.TH "SDIG" "1" "Oct 07, 2026" "" "PowerDNS Authoritative Server" .SH NAME sdig \- Perform a DNS query and show the results .SH SYNOPSIS diff -Nru pdns-4.9.17/docs/zone2json.1 pdns-4.9.18/docs/zone2json.1 --- pdns-4.9.17/docs/zone2json.1 2026-08-05 13:26:01.000000000 +0000 +++ pdns-4.9.18/docs/zone2json.1 2026-10-07 06:11:32.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "ZONE2JSON" "1" "Aug 05, 2026" "" "PowerDNS Authoritative Server" +.TH "ZONE2JSON" "1" "Oct 07, 2026" "" "PowerDNS Authoritative Server" .SH NAME zone2json \- convert BIND zones to JSON .SH SYNOPSIS diff -Nru pdns-4.9.17/docs/zone2ldap.1 pdns-4.9.18/docs/zone2ldap.1 --- pdns-4.9.17/docs/zone2ldap.1 2026-08-05 13:26:01.000000000 +0000 +++ pdns-4.9.18/docs/zone2ldap.1 2026-10-07 06:11:32.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "ZONE2LDAP" "1" "Aug 05, 2026" "" "PowerDNS Authoritative Server" +.TH "ZONE2LDAP" "1" "Oct 07, 2026" "" "PowerDNS Authoritative Server" .SH NAME zone2ldap \- convert zonefiles to ldif .SH SYNOPSIS diff -Nru pdns-4.9.17/docs/zone2sql.1 pdns-4.9.18/docs/zone2sql.1 --- pdns-4.9.17/docs/zone2sql.1 2026-08-05 13:26:01.000000000 +0000 +++ pdns-4.9.18/docs/zone2sql.1 2026-10-07 06:11:32.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "ZONE2SQL" "1" "Aug 05, 2026" "" "PowerDNS Authoritative Server" +.TH "ZONE2SQL" "1" "Oct 07, 2026" "" "PowerDNS Authoritative Server" .SH NAME zone2sql \- convert BIND zones to SQL .SH SYNOPSIS diff -Nru pdns-4.9.17/modules/geoipbackend/geoipbackend.cc pdns-4.9.18/modules/geoipbackend/geoipbackend.cc --- pdns-4.9.17/modules/geoipbackend/geoipbackend.cc 2026-08-05 13:24:42.000000000 +0000 +++ pdns-4.9.18/modules/geoipbackend/geoipbackend.cc 2026-10-07 06:09:31.000000000 +0000 @@ -39,7 +39,7 @@ #include #pragma GCC diagnostic pop -ReadWriteLock GeoIPBackend::s_state_lock; +SharedLockGuarded GeoIPBackend::s_state; struct GeoIPDNSResourceRecord : DNSResourceRecord { @@ -65,9 +65,6 @@ map custom_mapping; }; -static vector s_domains; -static int s_rc = 0; // refcount - always accessed under lock - const static std::array GeoIP_WEEKDAYS = {"mon", "tue", "wed", "thu", "fri", "sat", "sun"}; const static std::array GeoIP_MONTHS = {"jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"}; @@ -82,7 +79,7 @@ GeoIPBackend::GeoIPBackend(const string& suffix) { - WriteLock writeLock(&s_state_lock); + auto state = s_state.write_lock(); setArgPrefix("geoip" + suffix); if (!getArg("dnssec-keydir").empty()) { auto dirHandle = std::unique_ptr(opendir(getArg("dnssec-keydir").c_str()), closedir); @@ -91,16 +88,14 @@ } d_dnssec = true; } - if (s_rc == 0) { // first instance gets to open everything - initialize(); + if (state->instance_count == 0) { // first instance gets to open everything + initialize(*state); } - s_rc++; + state->instance_count++; } -static vector> s_geoip_files; - string getGeoForLua(const std::string& ip, int qaint); -static string queryGeoIP(const Netmask& addr, GeoIPInterface::GeoIPQueryAttribute attribute, GeoIPNetmask& gl); +static string queryGeoIP(const GeoIPBackend::filevec_t& geoip_files, const Netmask& addr, GeoIPInterface::GeoIPQueryAttribute attribute, GeoIPNetmask& gl); // validateMappingLookupFormats validates any custom format provided by the // user does not use the custom mapping placeholder again, else it would do an @@ -373,22 +368,22 @@ } } -void GeoIPBackend::initialize() +void GeoIPBackend::initialize(state_t& state) { YAML::Node config; vector tmp_domains; - s_geoip_files.clear(); // reset pointers + state.geoip_files.clear(); // reset pointers if (getArg("database-files").empty() == false) { vector files; stringtok(files, getArg("database-files"), " ,\t\r\n"); for (auto const& file : files) { - s_geoip_files.push_back(GeoIPInterface::makeInterface(file)); + state.geoip_files.push_back(GeoIPInterface::makeInterface(file)); } } - if (s_geoip_files.empty()) { + if (state.geoip_files.empty()) { g_log << Logger::Warning << "No GeoIP database files loaded!" << endl; } @@ -424,8 +419,8 @@ loadDomainsFromDirectory(domain_dir.as(), tmp_domains); } - s_domains.clear(); - std::swap(s_domains, tmp_domains); + state.domains.clear(); + std::swap(state.domains, tmp_domains); extern std::function g_getGeo; g_getGeo = getGeoForLua; @@ -434,18 +429,18 @@ GeoIPBackend::~GeoIPBackend() { try { - WriteLock writeLock(&s_state_lock); - s_rc--; - if (s_rc == 0) { // last instance gets to cleanup - s_geoip_files.clear(); - s_domains.clear(); + auto state = s_state.write_lock(); + state->instance_count--; + if (state->instance_count == 0) { // last instance gets to cleanup + state->geoip_files.clear(); + state->domains.clear(); } } catch (...) { } } -bool GeoIPBackend::lookup_static(const GeoIPDomain& dom, const DNSName& search, const QType& qtype, const DNSName& qdomain, const Netmask& addr, GeoIPNetmask& gl) +bool GeoIPBackend::lookup_static(const filevec_t& geoip_files, const GeoIPDomain& dom, const DNSName& search, const QType& qtype, const DNSName& qdomain, const Netmask& addr, GeoIPNetmask& gl) { const auto& i = dom.records.find(search); map cumul_probabilities; @@ -464,7 +459,7 @@ if (rr.weight == 0 || probability_rnd < comp || probability_rnd > (comp + rr.weight)) continue; } - const string& content = format2str(rr.content, addr, gl, dom); + const string& content = format2str(geoip_files, rr.content, addr, gl, dom); if (rr.qtype != QType::ENT && rr.qtype != QType::TXT && content.empty()) continue; d_result.push_back(rr); @@ -487,7 +482,7 @@ void GeoIPBackend::lookup(const QType& qtype, const DNSName& qdomain, int zoneId, DNSPacket* pkt_p) { - ReadLock rl(&s_state_lock); + auto state = s_state.read_lock(); const GeoIPDomain* dom; GeoIPNetmask gl; bool found = false; @@ -497,10 +492,11 @@ d_result.clear(); - if (zoneId > -1 && zoneId < static_cast(s_domains.size())) - dom = &(s_domains[zoneId]); + if (zoneId > -1 && zoneId < static_cast(state->domains.size())) { + dom = &(state->domains[zoneId]); + } else { - for (const GeoIPDomain& i : s_domains) { // this is arguably wrong, we should probably find the most specific match + for (const GeoIPDomain& i : state->domains) { // this is arguably wrong, we should probably find the most specific match if (qdomain.isPartOf(i.domain)) { dom = &i; found = true; @@ -517,7 +513,7 @@ gl.netmask = 0; - (void)this->lookup_static(*dom, qdomain, qtype, qdomain, addr, gl); + (void)this->lookup_static(state->geoip_files, *dom, qdomain, qtype, qdomain, addr, gl); const auto& target = (*dom).services.find(qdomain); if (target == (*dom).services.end()) @@ -534,7 +530,7 @@ GeoIPNetmask tmp_gl; tmp_gl.netmask = 0; // get netmask from geoip backend - if (queryGeoIP(addr, GeoIPInterface::Name, tmp_gl) == "unknown") { + if (queryGeoIP(state->geoip_files, addr, GeoIPInterface::Name, tmp_gl) == "unknown") { if (addr.isIPv6()) gl.netmask = target->second.netmask6; else @@ -550,10 +546,10 @@ // note that this means the array format won't work with indirect for (auto it = node->second.begin(); it != node->second.end(); it++) { - sformat = DNSName(format2str(*it, addr, gl, *dom)); + sformat = DNSName(format2str(state->geoip_files, *it, addr, gl, *dom)); // see if the record can be found - if (this->lookup_static((*dom), sformat, qtype, qdomain, addr, gl)) + if (this->lookup_static(state->geoip_files, (*dom), sformat, qtype, qdomain, addr, gl)) return; } @@ -591,11 +587,11 @@ return true; } -static string queryGeoIP(const Netmask& addr, GeoIPInterface::GeoIPQueryAttribute attribute, GeoIPNetmask& gl) +static string queryGeoIP(const GeoIPBackend::filevec_t& geoip_files, const Netmask& addr, GeoIPInterface::GeoIPQueryAttribute attribute, GeoIPNetmask& gl) { string ret = "unknown"; - for (auto const& gi : s_geoip_files) { + for (auto const& gi : geoip_files) { string val; const string ip = addr.toStringNoMask(); bool found = false; @@ -669,11 +665,12 @@ string getGeoForLua(const std::string& ip, int qaint) { + auto state = GeoIPBackend::s_state.read_lock(); GeoIPInterface::GeoIPQueryAttribute qa((GeoIPInterface::GeoIPQueryAttribute)qaint); try { const Netmask addr{ip}; GeoIPNetmask gl; - string res = queryGeoIP(addr, qa, gl); + string res = queryGeoIP(state->geoip_files, addr, qa, gl); // cout<<"Result for "<& alt, boost::optional& prec) { - for (auto const& gi : s_geoip_files) { + for (auto const& gi : geoip_files) { string val; if (addr.isIPv6()) { if (gi->queryLocationV6(gl, addr.toStringNoMask(), lat, lon, alt, prec)) @@ -703,7 +700,7 @@ return false; } -string GeoIPBackend::format2str(string sformat, const Netmask& addr, GeoIPNetmask& gl, const GeoIPDomain& dom) +string GeoIPBackend::format2str(const filevec_t& geoip_files, string sformat, const Netmask& addr, GeoIPNetmask& gl, const GeoIPDomain& dom) { string::size_type cur, last; boost::optional alt, prec; @@ -721,7 +718,7 @@ if (!sformat.compare(cur, 3, "%mp")) { rep = "unknown"; for (const auto& lookupFormat : dom.mapping_lookup_formats) { - auto it = dom.custom_mapping.find(format2str(lookupFormat, addr, gl, dom)); + auto it = dom.custom_mapping.find(format2str(geoip_files, lookupFormat, addr, gl, dom)); if (it != dom.custom_mapping.end()) { rep = it->second; break; @@ -729,34 +726,34 @@ } } else if (!sformat.compare(cur, 3, "%cn")) { - rep = queryGeoIP(addr, GeoIPInterface::Continent, tmp_gl); + rep = queryGeoIP(geoip_files, addr, GeoIPInterface::Continent, tmp_gl); } else if (!sformat.compare(cur, 3, "%co")) { - rep = queryGeoIP(addr, GeoIPInterface::Country, tmp_gl); + rep = queryGeoIP(geoip_files, addr, GeoIPInterface::Country, tmp_gl); } else if (!sformat.compare(cur, 3, "%cc")) { - rep = queryGeoIP(addr, GeoIPInterface::Country2, tmp_gl); + rep = queryGeoIP(geoip_files, addr, GeoIPInterface::Country2, tmp_gl); } else if (!sformat.compare(cur, 3, "%af")) { rep = (addr.isIPv6() ? "v6" : "v4"); } else if (!sformat.compare(cur, 3, "%as")) { - rep = queryGeoIP(addr, GeoIPInterface::ASn, tmp_gl); + rep = queryGeoIP(geoip_files, addr, GeoIPInterface::ASn, tmp_gl); } else if (!sformat.compare(cur, 3, "%re")) { - rep = queryGeoIP(addr, GeoIPInterface::Region, tmp_gl); + rep = queryGeoIP(geoip_files, addr, GeoIPInterface::Region, tmp_gl); } else if (!sformat.compare(cur, 3, "%na")) { - rep = queryGeoIP(addr, GeoIPInterface::Name, tmp_gl); + rep = queryGeoIP(geoip_files, addr, GeoIPInterface::Name, tmp_gl); } else if (!sformat.compare(cur, 3, "%ci")) { - rep = queryGeoIP(addr, GeoIPInterface::City, tmp_gl); + rep = queryGeoIP(geoip_files, addr, GeoIPInterface::City, tmp_gl); } else if (!sformat.compare(cur, 4, "%loc")) { char ns, ew; int d1, d2, m1, m2; double s1, s2; - if (!queryGeoLocation(addr, gl, lat, lon, alt, prec)) { + if (!queryGeoLocation(geoip_files, addr, gl, lat, lon, alt, prec)) { rep = ""; tmp_gl.netmask = (addr.isIPv6() ? 128 : 32); } @@ -783,7 +780,7 @@ nrep = 4; } else if (!sformat.compare(cur, 4, "%lat")) { - if (!queryGeoLocation(addr, gl, lat, lon, alt, prec)) { + if (!queryGeoLocation(geoip_files, addr, gl, lat, lon, alt, prec)) { rep = ""; tmp_gl.netmask = (addr.isIPv6() ? 128 : 32); } @@ -793,7 +790,7 @@ nrep = 4; } else if (!sformat.compare(cur, 4, "%lon")) { - if (!queryGeoLocation(addr, gl, lat, lon, alt, prec)) { + if (!queryGeoLocation(geoip_files, addr, gl, lat, lon, alt, prec)) { rep = ""; tmp_gl.netmask = (addr.isIPv6() ? 128 : 32); } @@ -870,10 +867,10 @@ void GeoIPBackend::reload() { - WriteLock wl(&s_state_lock); + auto state = s_state.write_lock(); try { - initialize(); + initialize(*state); } catch (PDNSException& pex) { g_log << Logger::Error << "GeoIP backend reload failed: " << pex.reason << endl; @@ -893,9 +890,9 @@ bool GeoIPBackend::getDomainInfo(const DNSName& domain, DomainInfo& di, bool /* getSerial */) { - ReadLock rl(&s_state_lock); + auto state = s_state.read_lock(); - for (GeoIPDomain dom : s_domains) { + for (const GeoIPDomain& dom : state->domains) { if (dom.domain == domain) { SOAData sd; this->getSOA(domain, sd); @@ -912,10 +909,10 @@ void GeoIPBackend::getAllDomains(vector* domains, bool /* getSerial */, bool /* include_disabled */) { - ReadLock rl(&s_state_lock); + auto state = s_state.read_lock(); DomainInfo di; - for (const auto& dom : s_domains) { + for (const auto& dom : state->domains) { SOAData sd; this->getSOA(dom.domain, sd); di.id = dom.id; @@ -932,8 +929,8 @@ if (!d_dnssec) return false; - ReadLock rl(&s_state_lock); - for (GeoIPDomain dom : s_domains) { + auto state = s_state.read_lock(); + for (const GeoIPDomain& dom : state->domains) { if (dom.domain == name) { if (hasDNSSECkey(dom.domain)) { meta[string("NSEC3NARROW")].push_back("1"); @@ -950,8 +947,8 @@ if (!d_dnssec) return false; - ReadLock rl(&s_state_lock); - for (GeoIPDomain dom : s_domains) { + auto state = s_state.read_lock(); + for (const GeoIPDomain& dom : state->domains) { if (dom.domain == name) { if (hasDNSSECkey(dom.domain)) { if (kind == "NSEC3NARROW") @@ -969,8 +966,8 @@ { if (!d_dnssec) return false; - ReadLock rl(&s_state_lock); - for (GeoIPDomain dom : s_domains) { + auto state = s_state.read_lock(); + for (const GeoIPDomain& dom : state->domains) { if (dom.domain == name) { regex_t reg; regmatch_t regm[5]; @@ -1013,10 +1010,9 @@ { if (!d_dnssec) return false; - WriteLock rl(&s_state_lock); - ostringstream path; + auto state = s_state.write_lock(); - for (GeoIPDomain dom : s_domains) { + for (const GeoIPDomain& dom : state->domains) { if (dom.domain == name) { regex_t reg; regmatch_t regm[5]; @@ -1049,10 +1045,10 @@ { if (!d_dnssec) return false; - WriteLock rl(&s_state_lock); + auto state = s_state.write_lock(); unsigned int nextid = 1; - for (GeoIPDomain dom : s_domains) { + for (const GeoIPDomain& dom : state->domains) { if (dom.domain == name) { regex_t reg; regmatch_t regm[5]; @@ -1098,8 +1094,8 @@ { if (!d_dnssec) return false; - WriteLock rl(&s_state_lock); - for (GeoIPDomain dom : s_domains) { + auto state = s_state.write_lock(); + for (const GeoIPDomain& dom : state->domains) { if (dom.domain == name) { regex_t reg; regmatch_t regm[5]; @@ -1133,8 +1129,8 @@ { if (!d_dnssec) return false; - WriteLock rl(&s_state_lock); - for (GeoIPDomain dom : s_domains) { + auto state = s_state.write_lock(); + for (const GeoIPDomain& dom : state->domains) { if (dom.domain == name) { regex_t reg; regmatch_t regm[5]; diff -Nru pdns-4.9.17/modules/geoipbackend/geoipbackend.hh pdns-4.9.18/modules/geoipbackend/geoipbackend.hh --- pdns-4.9.17/modules/geoipbackend/geoipbackend.hh 2026-08-05 13:24:42.000000000 +0000 +++ pdns-4.9.18/modules/geoipbackend/geoipbackend.hh 2026-10-07 06:09:31.000000000 +0000 @@ -35,8 +35,6 @@ #include "pdns/dnsbackend.hh" #include "pdns/lock.hh" -class GeoIPInterface; - namespace YAML { class Node; @@ -44,6 +42,8 @@ struct GeoIPDomain; +class GeoIPInterface; + struct GeoIPNetmask { int netmask; @@ -55,6 +55,18 @@ GeoIPBackend(const std::string& suffix = ""); ~GeoIPBackend() override; + using filevec_t = std::vector>; + using state_t = struct + { + unsigned int instance_count{0}; + std::vector domains; + filevec_t geoip_files; + }; + + // Needs to be public, for the Lua interface needs to access this outside + // of a backend instance. + static SharedLockGuarded s_state; + 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 get(DNSResourceRecord& r) override; @@ -76,18 +88,16 @@ bool unpublishDomainKey(const DNSName& name, unsigned int id) override; private: - static ReadWriteLock s_state_lock; - - void initialize(); - string format2str(string format, const Netmask& addr, GeoIPNetmask& gl, const GeoIPDomain& dom); + void initialize(state_t& state); + string format2str(const filevec_t& geoip_files, string format, const Netmask& addr, GeoIPNetmask& gl, const GeoIPDomain& dom); 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); + bool lookup_static(const filevec_t& geoip_files, 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& domains); + vector d_result; - vector d_files; std::vector d_global_mapping_lookup_formats; std::map d_global_custom_mapping; }; diff -Nru pdns-4.9.17/modules/lmdbbackend/lmdbbackend.cc pdns-4.9.18/modules/lmdbbackend/lmdbbackend.cc --- pdns-4.9.17/modules/lmdbbackend/lmdbbackend.cc 2026-08-05 13:24:42.000000000 +0000 +++ pdns-4.9.18/modules/lmdbbackend/lmdbbackend.cc 2026-10-07 06:09:31.000000000 +0000 @@ -1551,6 +1551,7 @@ DNSName relqname = qdomain.makeRelative(hunt); if (relqname.empty()) { + d_getcursor.reset(); return; } // cout<<"get will look for "<(id)), &(std::get<1>(id)), &a.theirSerial, &a.theirInception, &a.theirExpire, &(std::get<2>(id))); + auto& [zonename, remote, requestid] = id; + return d_resolver.tryGetSOASerial(&zonename, &remote, &a.theirSerial, &a.theirInception, &a.theirExpire, &requestid); } void deliverAnswer(const DomainNotificationInfo& dni, const Answer& a, unsigned int /* usec */) @@ -1281,6 +1283,7 @@ di.backend = tempdi.backend; } + const ComboAddress remote = *di.primaries.begin(); 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(); @@ -1290,10 +1293,10 @@ 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; + g_log << Logger::Warning << "Unable to retrieve SOA for " << di.zone << " from " << remote.toStringWithPortExcept(53) << ", this was the first time. NOTE: For every subsequent failed SOA check the domain will be suspended from freshness checks for 'num-errors x " << d_tickinterval << " seconds', with a maximum of " << (uint64_t)::arg().asNum("default-ttl") << " seconds. Skipping SOA checks until " << nextCheck << endl; } else if (newCount % 10 == 0) { - g_log << Logger::Notice << "Unable to retrieve SOA for " << di.zone << ", this was the " << std::to_string(newCount) << "th time. Skipping SOA checks until " << nextCheck << endl; + g_log << Logger::Notice << "Unable to retrieve SOA for " << di.zone << " from " << remote.toStringWithPortExcept(53) << ", 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) { @@ -1325,9 +1328,9 @@ catch (...) { } - uint32_t theirserial = ssr.d_freshness[di.id].theirSerial; + const auto& answer = ssr.d_freshness[di.id]; + uint32_t theirserial = answer.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; @@ -1352,23 +1355,23 @@ prio = SuckRequest::Notify; } - if (!maxInception && !ssr.d_freshness[di.id].theirInception) { + if (maxInception == 0 && answer.theirInception == 0) { 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) { + else if (maxInception == answer.theirInception && maxExpire == answer.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) { + else if (maxExpire >= now && answer.theirInception == 0) { 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) { + else if (maxInception != 0 && answer.theirInception == 0) { 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) { + else if (dk.doesDNSSEC() && maxInception == 0 && answer.theirInception != 0) { g_log << Logger::Notice << "Domain '" << di.zone << "' is stale, primary " << remote.toStringWithPortExcept(53) << " has signed, serial is " << ourserial << endl; addSuckRequest(di.zone, remote, prio); } diff -Nru pdns-4.9.17/pdns/backends/gsql/gsqlbackend.cc pdns-4.9.18/pdns/backends/gsql/gsqlbackend.cc --- pdns-4.9.17/pdns/backends/gsql/gsqlbackend.cc 2026-08-05 13:24:42.000000000 +0000 +++ pdns-4.9.18/pdns/backends/gsql/gsqlbackend.cc 2026-10-07 06:09:31.000000000 +0000 @@ -203,6 +203,148 @@ d_SearchCommentsQuery_stmt = nullptr; } +void GSQLBackend::allocateStatements() +{ + if (d_db) { + d_NoIdQuery_stmt = d_db->prepare(d_NoIdQuery, 2); + d_IdQuery_stmt = d_db->prepare(d_IdQuery, 3); + d_ANYNoIdQuery_stmt = d_db->prepare(d_ANYNoIdQuery, 1); + 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_PrimaryOfDomainsZoneQuery_stmt = d_db->prepare(d_PrimaryOfDomainsZoneQuery, 1); + d_InfoOfDomainsZoneQuery_stmt = d_db->prepare(d_InfoOfDomainsZoneQuery, 1); + 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_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_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); + d_DeleteNamesQuery_stmt = d_db->prepare(d_DeleteNamesQuery, 2); + d_firstOrderQuery_stmt = d_db->prepare(d_firstOrderQuery, 1); + d_beforeOrderQuery_stmt = d_db->prepare(d_beforeOrderQuery, 2); + d_afterOrderQuery_stmt = d_db->prepare(d_afterOrderQuery, 2); + d_lastOrderQuery_stmt = d_db->prepare(d_lastOrderQuery, 1); + d_updateOrderNameAndAuthQuery_stmt = d_db->prepare(d_updateOrderNameAndAuthQuery, 4); + d_updateOrderNameAndAuthTypeQuery_stmt = d_db->prepare(d_updateOrderNameAndAuthTypeQuery, 5); + d_nullifyOrderNameAndUpdateAuthQuery_stmt = d_db->prepare(d_nullifyOrderNameAndUpdateAuthQuery, 3); + d_nullifyOrderNameAndUpdateAuthTypeQuery_stmt = d_db->prepare(d_nullifyOrderNameAndUpdateAuthTypeQuery, 4); + d_RemoveEmptyNonTerminalsFromZoneQuery_stmt = d_db->prepare(d_RemoveEmptyNonTerminalsFromZoneQuery, 1); + d_DeleteEmptyNonTerminalQuery_stmt = d_db->prepare(d_DeleteEmptyNonTerminalQuery, 2); + d_AddDomainKeyQuery_stmt = d_db->prepare(d_AddDomainKeyQuery, 5); + d_GetLastInsertedKeyIdQuery_stmt = d_db->prepare(d_GetLastInsertedKeyIdQuery, 0); + d_ListDomainKeysQuery_stmt = d_db->prepare(d_ListDomainKeysQuery, 1); + d_GetAllDomainMetadataQuery_stmt = d_db->prepare(d_GetAllDomainMetadataQuery, 1); + d_GetDomainMetadataQuery_stmt = d_db->prepare(d_GetDomainMetadataQuery, 2); + d_ClearDomainMetadataQuery_stmt = d_db->prepare(d_ClearDomainMetadataQuery, 2); + d_ClearDomainAllMetadataQuery_stmt = d_db->prepare(d_ClearDomainAllMetadataQuery, 1); + d_SetDomainMetadataQuery_stmt = d_db->prepare(d_SetDomainMetadataQuery, 3); + d_RemoveDomainKeyQuery_stmt = d_db->prepare(d_RemoveDomainKeyQuery, 2); + d_ActivateDomainKeyQuery_stmt = d_db->prepare(d_ActivateDomainKeyQuery, 2); + d_DeactivateDomainKeyQuery_stmt = d_db->prepare(d_DeactivateDomainKeyQuery, 2); + d_PublishDomainKeyQuery_stmt = d_db->prepare(d_PublishDomainKeyQuery, 2); + d_UnpublishDomainKeyQuery_stmt = d_db->prepare(d_UnpublishDomainKeyQuery, 2); + d_ClearDomainAllKeysQuery_stmt = d_db->prepare(d_ClearDomainAllKeysQuery, 1); + d_getTSIGKeyQuery_stmt = d_db->prepare(d_getTSIGKeyQuery, 1); + d_setTSIGKeyQuery_stmt = d_db->prepare(d_setTSIGKeyQuery, 3); + d_deleteTSIGKeyQuery_stmt = d_db->prepare(d_deleteTSIGKeyQuery, 1); + d_getTSIGKeysQuery_stmt = d_db->prepare(d_getTSIGKeysQuery, 0); + d_getAllDomainsQuery_stmt = d_db->prepare(d_getAllDomainsQuery, 1); + d_ListCommentsQuery_stmt = d_db->prepare(d_ListCommentsQuery, 1); + d_InsertCommentQuery_stmt = d_db->prepare(d_InsertCommentQuery, 6); + d_DeleteCommentRRsetQuery_stmt = d_db->prepare(d_DeleteCommentRRsetQuery, 3); + d_DeleteCommentsQuery_stmt = d_db->prepare(d_DeleteCommentsQuery, 1); + d_SearchRecordsQuery_stmt = d_db->prepare(d_SearchRecordsQuery, 3); + d_SearchCommentsQuery_stmt = d_db->prepare(d_SearchCommentsQuery, 3); + } +} + +void GSQLBackend::freeStatements() +{ + d_NoIdQuery_stmt.reset(); + d_IdQuery_stmt.reset(); + d_ANYNoIdQuery_stmt.reset(); + d_ANYIdQuery_stmt.reset(); + d_listQuery_stmt.reset(); + d_listSubZoneQuery_stmt.reset(); + d_PrimaryOfDomainsZoneQuery_stmt.reset(); + d_InfoOfDomainsZoneQuery_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_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_InfoOfAllPrimaryDomainsQuery_stmt.reset(); + d_InfoProducerMembersQuery_stmt.reset(); + d_InfoConsumerMembersQuery_stmt.reset(); + d_DeleteDomainQuery_stmt.reset(); + d_DeleteZoneQuery_stmt.reset(); + d_DeleteRRSetQuery_stmt.reset(); + d_DeleteNamesQuery_stmt.reset(); + d_firstOrderQuery_stmt.reset(); + d_beforeOrderQuery_stmt.reset(); + d_afterOrderQuery_stmt.reset(); + d_lastOrderQuery_stmt.reset(); + d_updateOrderNameAndAuthQuery_stmt.reset(); + d_updateOrderNameAndAuthTypeQuery_stmt.reset(); + d_nullifyOrderNameAndUpdateAuthQuery_stmt.reset(); + d_nullifyOrderNameAndUpdateAuthTypeQuery_stmt.reset(); + d_RemoveEmptyNonTerminalsFromZoneQuery_stmt.reset(); + d_DeleteEmptyNonTerminalQuery_stmt.reset(); + d_AddDomainKeyQuery_stmt.reset(); + d_GetLastInsertedKeyIdQuery_stmt.reset(); + d_ListDomainKeysQuery_stmt.reset(); + d_GetAllDomainMetadataQuery_stmt.reset(); + d_GetDomainMetadataQuery_stmt.reset(); + d_ClearDomainMetadataQuery_stmt.reset(); + d_ClearDomainAllMetadataQuery_stmt.reset(); + d_SetDomainMetadataQuery_stmt.reset(); + d_RemoveDomainKeyQuery_stmt.reset(); + d_ActivateDomainKeyQuery_stmt.reset(); + d_DeactivateDomainKeyQuery_stmt.reset(); + d_PublishDomainKeyQuery_stmt.reset(); + d_UnpublishDomainKeyQuery_stmt.reset(); + d_ClearDomainAllKeysQuery_stmt.reset(); + d_getTSIGKeyQuery_stmt.reset(); + d_setTSIGKeyQuery_stmt.reset(); + d_deleteTSIGKeyQuery_stmt.reset(); + d_getTSIGKeysQuery_stmt.reset(); + d_getAllDomainsQuery_stmt.reset(); + d_ListCommentsQuery_stmt.reset(); + d_InsertCommentQuery_stmt.reset(); + d_DeleteCommentRRsetQuery_stmt.reset(); + d_DeleteCommentsQuery_stmt.reset(); + d_SearchRecordsQuery_stmt.reset(); + d_SearchCommentsQuery_stmt.reset(); +} + void GSQLBackend::setNotified(uint32_t domain_id, uint32_t serial) { try { @@ -1444,13 +1586,14 @@ throw PDNSException("GSQLBackend unable to lookup '" + qname.toLogString() + "|" + qtype.toString() + "':"+e.txtReason()); } - d_list=false; + d_currentQueryType = OTHER; d_qname=qname; } bool GSQLBackend::list(const DNSName &target, int domain_id, bool include_disabled) { - DLOG(g_log<<"GSQLBackend constructing handle for list of domain id '"<info(Logr::Debug, "preparing a list query", "domain id", Logging::Loggable(domain_id)))); try { reconnectIfNeeded(); @@ -1468,7 +1611,7 @@ throw PDNSException("GSQLBackend unable to list domain '" + target.toLogString() + "': "+e.txtReason()); } - d_list=true; + d_currentQueryType = LIST; d_qname.clear(); return true; @@ -1495,8 +1638,8 @@ throw PDNSException("GSQLBackend unable to list SubZones for domain '" + zone.toLogString() + "': "+e.txtReason()); } - d_list=false; - d_qname.clear(); + d_currentQueryType = LISTSUBZONE; + d_qname = zone; return true; } @@ -1506,11 +1649,10 @@ // g_log << "GSQLBackend get() was called for "<hasNextRow()) { + while ((*d_query_stmt)->hasNextRow()) { try { (*d_query_stmt)->nextRow(row); - if (!d_list) { + if (d_currentQueryType != LIST) { ASSERT_ROW_COLUMNS(d_query_name, row, 8); // lookup(), listSubZone() } else { @@ -1522,11 +1664,22 @@ try { extractRecord(row, r); } catch (...) { - goto skiprow; + continue; // skip this row + } + // Because not all backends allow for escaping in LIKE constructs (I'm + // looking at you, ODBC), the listSubZone query may return results which + // are not relevant to our needs, due to possible % or _ characters + // occurring in domain names and interpreted as wildcards by LIKE. + // We add a layer of filtering here. + if (d_currentQueryType == LISTSUBZONE) { + if (!r.qname.isPartOf(d_qname)) { + continue; // wrong result, skip this row + } } return true; } + // No more results try { (*d_query_stmt)->reset(); } catch (SSqlException &e) { @@ -2188,7 +2341,7 @@ } } -string GSQLBackend::pattern2SQLPattern(const string &pattern) +static string pattern2SQLPattern(const string &pattern) { string escaped_pattern = boost::replace_all_copy(pattern,"\\","\\\\"); boost::replace_all(escaped_pattern,"_","\\_"); @@ -2278,10 +2431,13 @@ else pdns::checked_stoi_into(r.ttl, row[1]); - if(!d_qname.empty()) + // d_qname is set for LISTSUBZONE queries for filtering, ignore it here + if (d_currentQueryType == OTHER && !d_qname.empty()) { r.qname=d_qname; - else + } + else { r.qname=DNSName(row[6]); + } r.qtype=row[3]; diff -Nru pdns-4.9.17/pdns/backends/gsql/gsqlbackend.hh pdns-4.9.18/pdns/backends/gsql/gsqlbackend.hh --- pdns-4.9.17/pdns/backends/gsql/gsqlbackend.hh 2026-08-05 13:24:42.000000000 +0000 +++ pdns-4.9.18/pdns/backends/gsql/gsqlbackend.hh 2026-10-07 06:09:31.000000000 +0000 @@ -52,146 +52,8 @@ } protected: - virtual void allocateStatements() - { - if (d_db) { - d_NoIdQuery_stmt = d_db->prepare(d_NoIdQuery, 2); - d_IdQuery_stmt = d_db->prepare(d_IdQuery, 3); - d_ANYNoIdQuery_stmt = d_db->prepare(d_ANYNoIdQuery, 1); - 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_PrimaryOfDomainsZoneQuery_stmt = d_db->prepare(d_PrimaryOfDomainsZoneQuery, 1); - d_InfoOfDomainsZoneQuery_stmt = d_db->prepare(d_InfoOfDomainsZoneQuery, 1); - 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_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_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); - d_DeleteNamesQuery_stmt = d_db->prepare(d_DeleteNamesQuery, 2); - d_firstOrderQuery_stmt = d_db->prepare(d_firstOrderQuery, 1); - d_beforeOrderQuery_stmt = d_db->prepare(d_beforeOrderQuery, 2); - d_afterOrderQuery_stmt = d_db->prepare(d_afterOrderQuery, 2); - d_lastOrderQuery_stmt = d_db->prepare(d_lastOrderQuery, 1); - d_updateOrderNameAndAuthQuery_stmt = d_db->prepare(d_updateOrderNameAndAuthQuery, 4); - d_updateOrderNameAndAuthTypeQuery_stmt = d_db->prepare(d_updateOrderNameAndAuthTypeQuery, 5); - d_nullifyOrderNameAndUpdateAuthQuery_stmt = d_db->prepare(d_nullifyOrderNameAndUpdateAuthQuery, 3); - d_nullifyOrderNameAndUpdateAuthTypeQuery_stmt = d_db->prepare(d_nullifyOrderNameAndUpdateAuthTypeQuery, 4); - d_RemoveEmptyNonTerminalsFromZoneQuery_stmt = d_db->prepare(d_RemoveEmptyNonTerminalsFromZoneQuery, 1); - d_DeleteEmptyNonTerminalQuery_stmt = d_db->prepare(d_DeleteEmptyNonTerminalQuery, 2); - d_AddDomainKeyQuery_stmt = d_db->prepare(d_AddDomainKeyQuery, 5); - d_GetLastInsertedKeyIdQuery_stmt = d_db->prepare(d_GetLastInsertedKeyIdQuery, 0); - d_ListDomainKeysQuery_stmt = d_db->prepare(d_ListDomainKeysQuery, 1); - d_GetAllDomainMetadataQuery_stmt = d_db->prepare(d_GetAllDomainMetadataQuery, 1); - d_GetDomainMetadataQuery_stmt = d_db->prepare(d_GetDomainMetadataQuery, 2); - d_ClearDomainMetadataQuery_stmt = d_db->prepare(d_ClearDomainMetadataQuery, 2); - d_ClearDomainAllMetadataQuery_stmt = d_db->prepare(d_ClearDomainAllMetadataQuery, 1); - d_SetDomainMetadataQuery_stmt = d_db->prepare(d_SetDomainMetadataQuery, 3); - d_RemoveDomainKeyQuery_stmt = d_db->prepare(d_RemoveDomainKeyQuery, 2); - d_ActivateDomainKeyQuery_stmt = d_db->prepare(d_ActivateDomainKeyQuery, 2); - d_DeactivateDomainKeyQuery_stmt = d_db->prepare(d_DeactivateDomainKeyQuery, 2); - d_PublishDomainKeyQuery_stmt = d_db->prepare(d_PublishDomainKeyQuery, 2); - d_UnpublishDomainKeyQuery_stmt = d_db->prepare(d_UnpublishDomainKeyQuery, 2); - d_ClearDomainAllKeysQuery_stmt = d_db->prepare(d_ClearDomainAllKeysQuery, 1); - d_getTSIGKeyQuery_stmt = d_db->prepare(d_getTSIGKeyQuery, 1); - d_setTSIGKeyQuery_stmt = d_db->prepare(d_setTSIGKeyQuery, 3); - d_deleteTSIGKeyQuery_stmt = d_db->prepare(d_deleteTSIGKeyQuery, 1); - d_getTSIGKeysQuery_stmt = d_db->prepare(d_getTSIGKeysQuery, 0); - d_getAllDomainsQuery_stmt = d_db->prepare(d_getAllDomainsQuery, 1); - d_ListCommentsQuery_stmt = d_db->prepare(d_ListCommentsQuery, 1); - d_InsertCommentQuery_stmt = d_db->prepare(d_InsertCommentQuery, 6); - d_DeleteCommentRRsetQuery_stmt = d_db->prepare(d_DeleteCommentRRsetQuery, 3); - d_DeleteCommentsQuery_stmt = d_db->prepare(d_DeleteCommentsQuery, 1); - d_SearchRecordsQuery_stmt = d_db->prepare(d_SearchRecordsQuery, 3); - d_SearchCommentsQuery_stmt = d_db->prepare(d_SearchCommentsQuery, 3); - } - } - - virtual void freeStatements() { - d_NoIdQuery_stmt.reset(); - d_IdQuery_stmt.reset(); - d_ANYNoIdQuery_stmt.reset(); - d_ANYIdQuery_stmt.reset(); - d_listQuery_stmt.reset(); - d_listSubZoneQuery_stmt.reset(); - d_PrimaryOfDomainsZoneQuery_stmt.reset(); - d_InfoOfDomainsZoneQuery_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_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_InfoOfAllPrimaryDomainsQuery_stmt.reset(); - d_InfoProducerMembersQuery_stmt.reset(); - d_InfoConsumerMembersQuery_stmt.reset(); - d_DeleteDomainQuery_stmt.reset(); - d_DeleteZoneQuery_stmt.reset(); - d_DeleteRRSetQuery_stmt.reset(); - d_DeleteNamesQuery_stmt.reset(); - d_firstOrderQuery_stmt.reset(); - d_beforeOrderQuery_stmt.reset(); - d_afterOrderQuery_stmt.reset(); - d_lastOrderQuery_stmt.reset(); - d_updateOrderNameAndAuthQuery_stmt.reset(); - d_updateOrderNameAndAuthTypeQuery_stmt.reset(); - d_nullifyOrderNameAndUpdateAuthQuery_stmt.reset(); - d_nullifyOrderNameAndUpdateAuthTypeQuery_stmt.reset(); - d_RemoveEmptyNonTerminalsFromZoneQuery_stmt.reset(); - d_DeleteEmptyNonTerminalQuery_stmt.reset(); - d_AddDomainKeyQuery_stmt.reset(); - d_GetLastInsertedKeyIdQuery_stmt.reset(); - d_ListDomainKeysQuery_stmt.reset(); - d_GetAllDomainMetadataQuery_stmt.reset(); - d_GetDomainMetadataQuery_stmt.reset(); - d_ClearDomainMetadataQuery_stmt.reset(); - d_ClearDomainAllMetadataQuery_stmt.reset(); - d_SetDomainMetadataQuery_stmt.reset(); - d_RemoveDomainKeyQuery_stmt.reset(); - d_ActivateDomainKeyQuery_stmt.reset(); - d_DeactivateDomainKeyQuery_stmt.reset(); - d_PublishDomainKeyQuery_stmt.reset(); - d_UnpublishDomainKeyQuery_stmt.reset(); - d_ClearDomainAllKeysQuery_stmt.reset(); - d_getTSIGKeyQuery_stmt.reset(); - d_setTSIGKeyQuery_stmt.reset(); - d_deleteTSIGKeyQuery_stmt.reset(); - d_getTSIGKeysQuery_stmt.reset(); - d_getAllDomainsQuery_stmt.reset(); - d_ListCommentsQuery_stmt.reset(); - d_InsertCommentQuery_stmt.reset(); - d_DeleteCommentRRsetQuery_stmt.reset(); - d_DeleteCommentsQuery_stmt.reset(); - d_SearchRecordsQuery_stmt.reset(); - d_SearchCommentsQuery_stmt.reset(); - } + virtual void allocateStatements(); + virtual void freeStatements(); public: void lookup(const QType &, const DNSName &qdomain, int zoneId, DNSPacket *p=nullptr) override; @@ -258,7 +120,6 @@ bool searchComments(const string &pattern, size_t maxResults, vector& result) override; protected: - string pattern2SQLPattern(const string& pattern); void extractRecord(SSqlStatement::row_t& row, DNSResourceRecord& rr); void extractComment(SSqlStatement::row_t& row, Comment& c); void setLastCheck(uint32_t domain_id, time_t lastcheck); @@ -282,7 +143,7 @@ return d_inTransaction; } - bool d_list{false}; + enum { LIST, LISTSUBZONE, OTHER } d_currentQueryType{OTHER}; string d_query_name; DNSName d_qname; SSqlStatement::result_t d_result; diff -Nru pdns-4.9.17/pdns/dnsname.cc pdns-4.9.18/pdns/dnsname.cc --- pdns-4.9.17/pdns/dnsname.cc 2026-08-05 13:24:42.000000000 +0000 +++ pdns-4.9.18/pdns/dnsname.cc 2026-10-07 06:09:31.000000000 +0000 @@ -31,6 +31,9 @@ const DNSName g_rootdnsname("."), g_wildcarddnsname("*"); +uint16_t DNSName::s_maxPointersToPointers = 4; +uint16_t DNSName::s_maxPointersPerName = 10; + /* raw storage in DNS label format, with trailing 0. W/o trailing 0, we are 'empty' www.powerdns.com = 3www8powerdns3com0 @@ -99,7 +102,7 @@ } } -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) +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, size_t* pointerBudget) { if (offset >= len) throw std::range_error("Trying to read past the end of the buffer ("+std::to_string(offset)+ " >= "+std::to_string(len)+")"); @@ -110,7 +113,7 @@ } } - packetParser(pos, len, offset, uncompress, qtype, qclass, consumed, 0, minOffset); + packetParser(pos, len, offset, uncompress, qtype, qclass, consumed, 0, minOffset, 0, pointerBudget); } static void checkLabelLength(uint8_t length) @@ -183,7 +186,7 @@ } // 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) +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, uint16_t pointerChainLength, size_t *pointerBudget) { if (offset >= len) { throw std::range_error("Trying to read past the end of the buffer ("+std::to_string(offset)+ " >= "+std::to_string(len)+")"); @@ -197,7 +200,24 @@ UnsignedCharView view(qpos, len); auto pos = parsePacketUncompressed(view, offset, uncompress); + labellen = view.at(pos); + if (pos == offset && labellen != 0) { + // It's a pointer + if (pointerBudget != nullptr) { + if (*pointerBudget == 0) { + throw std::range_error("Too many compression pointers processed in packet"); + } + (*pointerBudget)--; + } + // Are we chasing a pointer chain already for this name? + if (pointerChainLength++ > s_maxPointersToPointers) { + throw std::range_error("Too many pointer to pointers during decompression (" + std::to_string(pointerChainLength) + ")"); + } + } + else { + pointerChainLength = 0; // not an immediate pointer to pointer case any more + } pos++; if (labellen != 0 && pos < view.size()) { if (labellen < 0xc0) { @@ -219,11 +239,11 @@ 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"); + if (++depth > s_maxPointersPerName) { + throw std::range_error("Abort label decompression after too many redirects (" + std::to_string(depth) + ")"); } - packetParser(qpos, len, newpos, true, nullptr, nullptr, nullptr, depth, minOffset); + packetParser(qpos, len, newpos, true, nullptr, nullptr, nullptr, depth, minOffset, pointerChainLength, pointerBudget); pos++; } diff -Nru pdns-4.9.17/pdns/dnsname.hh pdns-4.9.18/pdns/dnsname.hh --- pdns-4.9.17/pdns/dnsname.hh 2026-08-05 13:24:42.000000000 +0000 +++ pdns-4.9.18/pdns/dnsname.hh 2026-10-07 06:09:31.000000000 +0000 @@ -78,7 +78,10 @@ class DNSName { public: - static const size_t s_maxDNSNameLength = 255; + static constexpr size_t s_maxDNSNameLength = 255; + static constexpr size_t s_maxDNSLabelLength = 63; + static uint16_t s_maxPointersToPointers; + static uint16_t s_maxPointersPerName; 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 @@ -100,7 +103,7 @@ DNSName(DNSName&& a) = default; 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. + DNSName(const char* pos, size_t len, size_t offset, bool uncompress, uint16_t* qtype = nullptr, uint16_t* qclass = nullptr, unsigned int* consumed = nullptr, uint16_t minOffset = 0, size_t* pointerBudget = nullptr); //!< 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 @@ -236,7 +239,7 @@ 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); + 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, uint16_t pointerChainLength, size_t* pointerBudget); 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); diff -Nru pdns-4.9.17/pdns/dnsparser.cc pdns-4.9.18/pdns/dnsparser.cc --- pdns-4.9.17/pdns/dnsparser.cc 2026-08-05 13:24:42.000000000 +0000 +++ pdns-4.9.18/pdns/dnsparser.cc 2026-10-07 06:09:31.000000000 +0000 @@ -224,7 +224,7 @@ return resourceRecord; } -void MOADNSParser::init(bool query, const std::string_view& packet) +void MOADNSParser::init(bool query, const std::string_view& packet, size_t pointerBudget) { if (packet.size() < sizeof(dnsheader)) throw MOADNSException("Packet shorter than minimal header"); @@ -245,6 +245,9 @@ unsigned int n=0; PacketReader pr(packet); + if (pointerBudget != 0) { + pr.setPointerBudget(pointerBudget); + } bool validPacket=false; try { d_qtype = d_qclass = 0; // sometimes replies come in with no question, don't present garbage then @@ -354,6 +357,8 @@ return false; } +size_t PacketReader::s_MaxPointersPerByte = 4; + void PacketReader::getDnsrecordheader(struct dnsrecordheader &ah) { unsigned char *p = reinterpret_cast(&ah); @@ -455,12 +460,12 @@ DNSName PacketReader::getName() { - unsigned int consumed; + unsigned int consumed{}; try { - DNSName dn((const char*) d_content.data(), d_content.size(), d_pos, true /* uncompress */, nullptr /* qtype */, nullptr /* qclass */, &consumed, sizeof(dnsheader)); + DNSName name((const char*) d_content.data(), d_content.size(), d_pos, true /* uncompress */, nullptr /* qtype */, nullptr /* qclass */, &consumed, sizeof(dnsheader), &d_pointerBudget); - d_pos+=consumed; - return dn; + d_pos += consumed; + return name; } catch(const std::range_error& re) { throw std::out_of_range(string("dnsname issue: ")+re.what()); diff -Nru pdns-4.9.17/pdns/dnsparser.hh pdns-4.9.18/pdns/dnsparser.hh --- pdns-4.9.17/pdns/dnsparser.hh 2026-08-05 13:24:42.000000000 +0000 +++ pdns-4.9.18/pdns/dnsparser.hh 2026-10-07 06:09:31.000000000 +0000 @@ -70,11 +70,19 @@ 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::max()) + if (content.size() > std::numeric_limits::max()) { throw std::out_of_range("packet too large"); + } - d_recordlen = (uint16_t) content.size(); + d_recordlen = static_cast(content.size()); not_used = 0; + d_pointerBudget = static_cast(d_recordlen) * s_MaxPointersPerByte; + } + + // Default is s_MaxPointersPerByte * packet size + void setPointerBudget(size_t maxPointers) + { + d_pointerBudget = maxPointers; } uint32_t get32BitInt(); @@ -179,7 +187,10 @@ d_pos += n; } + static size_t s_MaxPointersPerByte; + private: + size_t d_pointerBudget{0}; uint16_t d_pos; uint16_t d_startrecordpos; // needed for getBlob later on uint16_t d_recordlen; // ditto @@ -465,9 +476,9 @@ } //! Parse from a pointer and length - MOADNSParser(bool query, const char *packet, unsigned int len) : d_tsigPos(0) + MOADNSParser(bool query, const char *packet, unsigned int len, size_t pointerBudget = 0) : d_tsigPos(0) { - init(query, std::string_view(packet, len)); + init(query, std::string_view(packet, len), pointerBudget); } DNSName d_qname; @@ -488,7 +499,7 @@ bool hasEDNS() const; private: - void init(bool query, const std::string_view& packet); + void init(bool query, const std::string_view& packet, size_t pointerBudget = 0); uint16_t d_tsigPos; }; diff -Nru pdns-4.9.17/pdns/lua-record.cc pdns-4.9.18/pdns/lua-record.cc --- pdns-4.9.17/pdns/lua-record.cc 2026-08-05 13:24:42.000000000 +0000 +++ pdns-4.9.18/pdns/lua-record.cc 2026-10-07 06:09:31.000000000 +0000 @@ -1,12 +1,20 @@ -#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include #include +#include #include -#include -#include -#include +#include + +#include "misc.hh" #include "qtype.hh" -#include #include "version.hh" #include "ext/luawrapper/include/LuaContext.hpp" #include "lock.hh" @@ -77,12 +85,14 @@ std::atomic lastAccess{0}; }; + bool d_stop{false}; + public: IsUpOracle() { d_checkerThreadStarted.clear(); } - ~IsUpOracle() = default; + ~IsUpOracle(); 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); @@ -172,11 +182,10 @@ void checkThread() { setThreadName("pdns/luaupcheck"); - while (true) - { + while (!d_stop) { std::chrono::system_clock::time_point checkStart = std::chrono::system_clock::now(); - std::vector> results; - std::vector toDelete; + std::forward_list> results; + std::forward_list toDelete; { // make sure there's no insertion auto statuses = d_statuses.read_lock(); @@ -185,12 +194,12 @@ auto& state = it.second; if (desc.url.empty()) { // TCP - results.push_back(std::async(std::launch::async, &IsUpOracle::checkTCP, this, desc, state->status.load(), state->first.load())); + results.push_front(std::async(std::launch::async, &IsUpOracle::checkTCP, this, desc, state->status.load(), state->first.load())); } else { // URL - results.push_back(std::async(std::launch::async, &IsUpOracle::checkURL, this, desc, state->status.load(), state->first.load())); + results.push_front(std::async(std::launch::async, &IsUpOracle::checkURL, this, desc, state->status.load(), state->first.load())); } if (std::chrono::system_clock::from_time_t(state->lastAccess) < (checkStart - std::chrono::seconds(g_luaHealthChecksExpireDelay))) { - toDelete.push_back(desc); + toDelete.push_front(desc); } } } @@ -198,17 +207,31 @@ for (auto& future: results) { future.wait(); } + // No need to keep these objects around any further + results.clear(); if (!toDelete.empty()) { - auto statuses = d_statuses.write_lock(); - for (auto& it: toDelete) { - statuses->erase(it); + { + auto statuses = d_statuses.write_lock(); + for (auto& iter: toDelete) { + statuses->erase(iter); + } } + // No need to keep these objects around while we'll be waiting below. + toDelete.clear(); } // set thread name again, in case std::async surprised us by doing work in this thread setThreadName("pdns/luaupcheck"); - std::this_thread::sleep_until(checkStart + std::chrono::seconds(g_luaHealthChecksInterval)); + // Wait for at most one complete check interval, but allow an earlier + // wakeup in case more work is being put in d_statuses. + { + std::unique_lock lock(d_mutex); + auto sleepTime = std::chrono::seconds(g_luaHealthChecksInterval) - (std::chrono::system_clock::now() - checkStart); + if (sleepTime > std::chrono::seconds::zero()) { + d_condvar.wait_until(lock, std::chrono::system_clock::now() + sleepTime); + } + } } } @@ -218,6 +241,9 @@ std::unique_ptr d_checkerThread; std::atomic_flag d_checkerThreadStarted; + std::mutex d_mutex; // used with the condition variable below + std::condition_variable d_condvar; + void setStatus(const CheckDesc& cd, bool status) { auto statuses = d_statuses.write_lock(); @@ -254,9 +280,6 @@ bool IsUpOracle::isUp(const CheckDesc& cd) { - if (!d_checkerThreadStarted.test_and_set()) { - d_checkerThread = std::make_unique([this] { return checkThread(); }); - } time_t now = time(nullptr); { auto statuses = d_statuses.read_lock(); @@ -277,6 +300,14 @@ (*statuses)[cd] = std::make_unique(now); } } + // Now that we have given it work to do, make sure the checker thread runs, + // and notify it if it had already been running. + if (!d_checkerThreadStarted.test_and_set()) { + d_checkerThread = std::make_unique([this] { return checkThread(); }); + } + else { + d_condvar.notify_all(); + } return false; } @@ -292,6 +323,16 @@ return isUp(cd); } +IsUpOracle::~IsUpOracle() +{ + d_stop = true; + // If the checker thread has been started, tell it to stop now. + if (d_checkerThreadStarted.test_and_set()) { + d_condvar.notify_all(); + d_checkerThread->join(); + } +} + IsUpOracle g_up; namespace { template @@ -1332,6 +1373,9 @@ */ lua.writeFunction("view", [](const vector > > >& in) { for(const auto& rule : in) { + if (rule.second.size() < 2) { + throw std::invalid_argument("Invalid view data"); + } const auto& netmasks=rule.second[0].second; const auto& destinations=rule.second[1].second; for(const auto& nmpair : netmasks) { diff -Nru pdns-4.9.17/pdns/test-dnsname_cc.cc pdns-4.9.18/pdns/test-dnsname_cc.cc --- pdns-4.9.17/pdns/test-dnsname_cc.cc 2026-08-05 13:24:42.000000000 +0000 +++ pdns-4.9.18/pdns/test-dnsname_cc.cc 2026-10-07 06:09:31.000000000 +0000 @@ -955,11 +955,33 @@ BOOST_AUTO_TEST_CASE(test_compression_loop2) { // Compression loop (deep recursion) int i; + DNSName::s_maxPointersToPointers = 98; + DNSName::s_maxPointersPerName = 99; string name("\x00\xc0\x00", 3); for (i=0; i<98; ++i) { name.append( 1, ((i >> 7) & 0xff) | 0xc0); name.append( 1, ((i << 1) & 0xff) | 0x01); } + size_t budget = 99; + BOOST_CHECK_NO_THROW(DNSName dn(name.c_str(), name.size(), name.size()-2, true, nullptr, nullptr, nullptr, 0, &budget)); + + ++i; + name.append( 1, ((i >> 7) & 0xff) | 0xc0); + name.append( 1, ((i << 1) & 0xff) | 0x01); + + BOOST_CHECK_THROW(DNSName dn(name.c_str(), name.size(), name.size()-2, true), std::range_error); + DNSName::s_maxPointersToPointers = 4; + DNSName::s_maxPointersPerName = 10; +} + +BOOST_AUTO_TEST_CASE(test_compression_loop3) { // Compression loop (direct pointer to pointer) + + int i; + string name("\x00\xc0\x00", 3); + for (i=0; i<2; ++i) { + name.append( 1, ((i >> 7) & 0xff) | 0xc0); + name.append( 1, ((i << 1) & 0xff) | 0x01); + } BOOST_CHECK_NO_THROW(DNSName dn(name.c_str(), name.size(), name.size()-2, true)); ++i; diff -Nru pdns-4.9.17/pdns/test-dnsparser_cc.cc pdns-4.9.18/pdns/test-dnsparser_cc.cc --- pdns-4.9.17/pdns/test-dnsparser_cc.cc 2026-08-05 13:24:42.000000000 +0000 +++ pdns-4.9.18/pdns/test-dnsparser_cc.cc 2026-10-07 06:09:31.000000000 +0000 @@ -616,4 +616,118 @@ } +BOOST_AUTO_TEST_CASE(test_compressionBudget) +{ + const std::vector data = { + 0x37, 0xa5, + 0x84, 0x00, 0x00, 0x01, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, + 0x02, 0x00, 0x01, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x07, 0xe9, 0x00, + 0x00, 0x14, 0x01, 0x61, 0x0c, 0x72, 0x6f, 0x6f, 0x74, 0x2d, 0x73, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x73, 0x03, 0x6e, 0x65, 0x74, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x01, 0x00, 0x07, 0xe9, 0x00, 0x00, 0x14, 0x01, 0x62, 0x0c, + 0x72, 0x6f, 0x6f, 0x74, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, + 0x03, 0x6e, 0x65, 0x74, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x07, + 0xe9, 0x00, 0x00, 0x14, 0x01, 0x63, 0x0c, 0x72, 0x6f, 0x6f, 0x74, 0x2d, + 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x03, 0x6e, 0x65, 0x74, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x07, 0xe9, 0x00, 0x00, 0x14, 0x01, + 0x64, 0x0c, 0x72, 0x6f, 0x6f, 0x74, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, + 0x72, 0x73, 0x03, 0x6e, 0x65, 0x74, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, + 0x00, 0x07, 0xe9, 0x00, 0x00, 0x14, 0x01, 0x65, 0x0c, 0x72, 0x6f, 0x6f, + 0x74, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x03, 0x6e, 0x65, + 0x74, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x07, 0xe9, 0x00, 0x00, + 0x14, 0x01, 0x66, 0x0c, 0x72, 0x6f, 0x6f, 0x74, 0x2d, 0x73, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x73, 0x03, 0x6e, 0x65, 0x74, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x01, 0x00, 0x07, 0xe9, 0x00, 0x00, 0x14, 0x01, 0x67, 0x0c, 0x72, + 0x6f, 0x6f, 0x74, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x03, + 0x6e, 0x65, 0x74, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x07, 0xe9, + 0x00, 0x00, 0x14, 0x01, 0x68, 0x0c, 0x72, 0x6f, 0x6f, 0x74, 0x2d, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x03, 0x6e, 0x65, 0x74, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x01, 0x00, 0x07, 0xe9, 0x00, 0x00, 0x14, 0x01, 0x69, + 0x0c, 0x72, 0x6f, 0x6f, 0x74, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x73, 0x03, 0x6e, 0x65, 0x74, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, + 0x07, 0xe9, 0x00, 0x00, 0x14, 0x01, 0x6a, 0x0c, 0x72, 0x6f, 0x6f, 0x74, + 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x03, 0x6e, 0x65, 0x74, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x07, 0xe9, 0x00, 0x00, 0x14, + 0x01, 0x6b, 0x0c, 0x72, 0x6f, 0x6f, 0x74, 0x2d, 0x73, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x73, 0x03, 0x6e, 0x65, 0x74, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x01, 0x00, 0x07, 0xe9, 0x00, 0x00, 0x14, 0x01, 0x6c, 0x0c, 0x72, 0x6f, + 0x6f, 0x74, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x03, 0x6e, + 0x65, 0x74, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x07, 0xe9, 0x00, + 0x00, 0x14, 0x01, 0x6d, 0x0c, 0x72, 0x6f, 0x6f, 0x74, 0x2d, 0x73, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x73, 0x03, 0x6e, 0x65, 0x74, 0x00, 0x00, 0x00, + 0x2e, 0x00, 0x01, 0x00, 0x07, 0xe9, 0x00, 0x01, 0x13, 0x00, 0x02, 0x08, + 0x00, 0x00, 0x07, 0xe9, 0x00, 0x6a, 0xbc, 0x97, 0x50, 0x6a, 0xab, 0x65, + 0xc0, 0xe1, 0xb4, 0x00, 0x06, 0x1c, 0xde, 0x4c, 0xe4, 0x37, 0xa9, 0x47, + 0xe4, 0xf5, 0x16, 0xfb, 0xb2, 0xd3, 0x8b, 0x2d, 0x96, 0xe5, 0xd3, 0x47, + 0x4b, 0xbe, 0xc1, 0x54, 0x05, 0xd6, 0x06, 0x23, 0xf0, 0x06, 0x8b, 0x25, + 0x01, 0x01, 0x74, 0x6a, 0xca, 0x5e, 0xc9, 0xef, 0x55, 0x2b, 0x97, 0x24, + 0x70, 0x0c, 0xd4, 0xc9, 0xe8, 0x93, 0x51, 0x65, 0xc0, 0xa6, 0xbf, 0x48, + 0xd8, 0xf7, 0xc5, 0xbf, 0xb5, 0xcd, 0xbe, 0x05, 0x77, 0x18, 0x45, 0xc0, + 0xbb, 0xf7, 0xe3, 0xdb, 0x6c, 0x27, 0x62, 0x73, 0xb6, 0xe7, 0x7e, 0xb7, + 0x94, 0xed, 0x7a, 0x77, 0x45, 0x9f, 0x42, 0x27, 0x0e, 0x40, 0x83, 0xe7, + 0x63, 0xcd, 0x61, 0xed, 0x1c, 0x46, 0x3c, 0x97, 0x52, 0x33, 0xbd, 0xb9, + 0xb4, 0x56, 0xe4, 0xe8, 0xb1, 0x7b, 0xeb, 0xe3, 0x9c, 0x05, 0xba, 0xcf, + 0xe0, 0x0a, 0x70, 0x1e, 0x5d, 0x1f, 0xb0, 0x84, 0xad, 0x8d, 0x53, 0x0d, + 0x9e, 0x8c, 0x33, 0x6e, 0xff, 0x37, 0x86, 0xd2, 0xf2, 0xd2, 0x1f, 0x7f, + 0x4e, 0xc9, 0xd7, 0x05, 0x8e, 0x6e, 0xcc, 0x47, 0x75, 0x36, 0x72, 0x23, + 0xee, 0xdb, 0x75, 0x28, 0x79, 0xd1, 0x37, 0x47, 0xdc, 0x6c, 0x78, 0xb5, + 0x92, 0x23, 0xb3, 0xaa, 0x86, 0x57, 0x53, 0x49, 0xa5, 0x36, 0xff, 0x97, + 0x0c, 0x7f, 0xda, 0xd2, 0x6e, 0xcd, 0x97, 0xf0, 0xbf, 0x44, 0x3b, 0x6e, + 0xec, 0xf0, 0x3d, 0x2b, 0xc5, 0x09, 0x7f, 0x7e, 0xc9, 0x5b, 0xe4, 0x29, + 0x5b, 0x1c, 0x84, 0xd0, 0x61, 0x6b, 0x57, 0x50, 0xc6, 0x95, 0x08, 0x22, + 0x2f, 0xad, 0x15, 0x94, 0x3a, 0xe9, 0x7b, 0x59, 0xcf, 0xf9, 0xf5, 0x67, + 0x6e, 0x01, 0x92, 0x76, 0x86, 0xdb, 0xc0, 0xd8, 0x0e, 0xdc, 0xfe, 0xd9, + 0x4d, 0xdc, 0xec, 0x6a, 0xfd, 0x11, 0xef, 0xff, 0xc9, 0xdc, 0x4b, 0x93, + 0x7d, 0x27, 0xfd, 0xc7, 0x7c, 0xfc, 0x2f, 0xa0, 0xc0, 0x1c, 0x00, 0x01, + 0x00, 0x01, 0x00, 0x07, 0xe9, 0x00, 0x00, 0x04, 0xc6, 0x29, 0x00, 0x04, + 0xc0, 0x1c, 0x00, 0x1c, 0x00, 0x01, 0x00, 0x07, 0xe9, 0x00, 0x00, 0x10, + 0x20, 0x01, 0x05, 0x03, 0xba, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x30, 0xc0, 0x3b, 0x00, 0x01, 0x00, 0x01, 0x00, 0x07, + 0xe9, 0x00, 0x00, 0x04, 0xaa, 0xf7, 0xaa, 0x02, 0xc0, 0x3b, 0x00, 0x1c, + 0x00, 0x01, 0x00, 0x07, 0xe9, 0x00, 0x00, 0x10, 0x28, 0x01, 0x01, 0xb8, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, + 0xc0, 0x5a, 0x00, 0x01, 0x00, 0x01, 0x00, 0x07, 0xe9, 0x00, 0x00, 0x04, + 0xc0, 0x21, 0x04, 0x0c, 0xc0, 0x5a, 0x00, 0x1c, 0x00, 0x01, 0x00, 0x07, + 0xe9, 0x00, 0x00, 0x10, 0x20, 0x01, 0x05, 0x00, 0x00, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0xc0, 0x79, 0x00, 0x01, + 0x00, 0x01, 0x00, 0x07, 0xe9, 0x00, 0x00, 0x04, 0xc7, 0x07, 0x5b, 0x0d, + 0xc0, 0x79, 0x00, 0x1c, 0x00, 0x01, 0x00, 0x07, 0xe9, 0x00, 0x00, 0x10, + 0x20, 0x01, 0x05, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0d, 0xc0, 0x98, 0x00, 0x01, 0x00, 0x01, 0x00, 0x07, + 0xe9, 0x00, 0x00, 0x04, 0xc0, 0xcb, 0xe6, 0x0a, 0xc0, 0x98, 0x00, 0x1c, + 0x00, 0x01, 0x00, 0x07, 0xe9, 0x00, 0x00, 0x10, 0x20, 0x01, 0x05, 0x00, + 0x00, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, + 0xc0, 0xb7, 0x00, 0x01, 0x00, 0x01, 0x00, 0x07, 0xe9, 0x00, 0x00, 0x04, + 0xc0, 0x05, 0x05, 0xf1, 0xc0, 0xb7, 0x00, 0x1c, 0x00, 0x01, 0x00, 0x07, + 0xe9, 0x00, 0x00, 0x10, 0x20, 0x01, 0x05, 0x00, 0x00, 0x2f, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xc0, 0xd6, 0x00, 0x01, + 0x00, 0x01, 0x00, 0x07, 0xe9, 0x00, 0x00, 0x04, 0xc0, 0x70, 0x24, 0x04, + 0xc0, 0xd6, 0x00, 0x1c, 0x00, 0x01, 0x00, 0x07, 0xe9, 0x00, 0x00, 0x10, + 0x20, 0x01, 0x05, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0d, 0x0d, 0xc0, 0xf5, 0x00, 0x01, 0x00, 0x01, 0x00, 0x07, + 0xe9, 0x00, 0x00, 0x04, 0xc6, 0x61, 0xbe, 0x35, 0xc0, 0xf5, 0x00, 0x1c, + 0x00, 0x01, 0x00, 0x07, 0xe9, 0x00, 0x00, 0x10, 0x20, 0x01, 0x05, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, + 0xc1, 0x14, 0x00, 0x01, 0x00, 0x01, 0x00, 0x07, 0xe9, 0x00, 0x00, 0x04, + 0xc0, 0x24, 0x94, 0x11, 0xc1, 0x14, 0x00, 0x1c, 0x00, 0x01, 0x00, 0x07, + 0xe9, 0x00, 0x00, 0x10, 0x20, 0x01, 0x07, 0xfe, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0xc1, 0x33, 0x00, 0x01, + 0x00, 0x01, 0x00, 0x07, 0xe9, 0x00, 0x00, 0x04, 0xc0, 0x3a, 0x80, 0x1e, + 0xc1, 0x33, 0x00, 0x1c, 0x00, 0x01, 0x00, 0x07, 0xe9, 0x00, 0x00, 0x10, + 0x20, 0x01, 0x05, 0x03, 0x0c, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x30, 0xc1, 0x52, 0x00, 0x01, 0x00, 0x01, 0x00, 0x07, + 0xe9, 0x00, 0x00, 0x04, 0xc1, 0x00, 0x0e, 0x81, 0xc1, 0x52, 0x00, 0x1c, + 0x00, 0x01, 0x00, 0x07, 0xe9, 0x00, 0x00, 0x10, 0x20, 0x01, 0x07, 0xfd, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xc1, 0x71, 0x00, 0x01, 0x00, 0x01, 0x00, 0x07, 0xe9, 0x00, 0x00, 0x04, + 0xc7, 0x07, 0x53, 0x2a, 0x00, 0x00, 0x29, 0x04, 0xd0, 0x00, 0x00, 0x80, + 0x00, 0x00, 0x00, 0x00}; + + const std::string packet(reinterpret_cast(data.data()), data.size()); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) it's the API + MOADNSParser parser1(false, packet); + BOOST_CHECK_EQUAL(parser1.d_answers.size(), 38U); // 38 auth/answer records, default budget is high + BOOST_CHECK_NO_THROW(MOADNSParser(false, packet.data(), packet.size(), 23U)); // just enough + BOOST_CHECK_THROW(MOADNSParser(false, packet.data(), packet.size(), 22U), MOADNSException); +} + BOOST_AUTO_TEST_SUITE_END()