Version in base suite: 5.2.7-0+deb13u1 Base version: pdns-recursor_5.2.7-0+deb13u1 Target version: pdns-recursor_5.2.8-0+deb13u1 Base file: /srv/ftp-master.debian.org/ftp/pool/main/p/pdns-recursor/pdns-recursor_5.2.7-0+deb13u1.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/p/pdns-recursor/pdns-recursor_5.2.8-0+deb13u1.dsc configure | 20 +-- configure.ac | 2 debian/changelog | 7 + effective_tld_names.dat | 205 +++++++++++++++++++++++++------------- lwres.cc | 2 lwres.hh | 1 pdns_recursor.1 | 2 pdns_recursor.cc | 3 pubsuffix.cc | 75 ++++++++----- rec-main.cc | 3 rec_control.1 | 2 recpacketcache.cc | 1 recpacketcache.hh | 1 recursor_cache.cc | 7 + recursor_cache.hh | 1 settings/cxxsettings-generated.cc | 32 +++++ settings/rust/src/lib.rs | 18 +++ settings/table.py | 40 +++++++ syncres.cc | 55 +++++++++- syncres.hh | 4 test-recursorcache_cc.cc | 1 test-syncres_cc.cc | 1 test-syncres_cc1.cc | 159 +++++++++++++++++++++++++++++ test-syncres_cc2.cc | 6 - 24 files changed, 530 insertions(+), 118 deletions(-) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpcc08znh_/pdns-recursor_5.2.7-0+deb13u1.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpcc08znh_/pdns-recursor_5.2.8-0+deb13u1.dsc: no acceptable signature found diff -Nru pdns-recursor-5.2.7/configure pdns-recursor-5.2.8/configure --- pdns-recursor-5.2.7/configure 2025-11-25 13:41:46.000000000 +0000 +++ pdns-recursor-5.2.8/configure 2026-01-21 11:13:07.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-recursor 5.2.7. +# Generated by GNU Autoconf 2.71 for pdns-recursor 5.2.8. # # # Copyright (C) 1992-1996, 1998-2017, 2020-2021 Free Software Foundation, @@ -618,8 +618,8 @@ # Identity of this package. PACKAGE_NAME='pdns-recursor' PACKAGE_TARNAME='pdns-recursor' -PACKAGE_VERSION='5.2.7' -PACKAGE_STRING='pdns-recursor 5.2.7' +PACKAGE_VERSION='5.2.8' +PACKAGE_STRING='pdns-recursor 5.2.8' PACKAGE_BUGREPORT='' PACKAGE_URL='' @@ -1588,7 +1588,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-recursor 5.2.7 to adapt to many kinds of systems. +\`configure' configures pdns-recursor 5.2.8 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1659,7 +1659,7 @@ if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of pdns-recursor 5.2.7:";; + short | recursive ) echo "Configuration of pdns-recursor 5.2.8:";; esac cat <<\_ACEOF @@ -1859,7 +1859,7 @@ test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -pdns-recursor configure 5.2.7 +pdns-recursor configure 5.2.8 generated by GNU Autoconf 2.71 Copyright (C) 2021 Free Software Foundation, Inc. @@ -2348,7 +2348,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-recursor $as_me 5.2.7, which was +It was created by pdns-recursor $as_me 5.2.8, which was generated by GNU Autoconf 2.71. Invocation command line was $ $0$ac_configure_args_raw @@ -3844,7 +3844,7 @@ # Define the identity of the package. PACKAGE='pdns-recursor' - VERSION='5.2.7' + VERSION='5.2.8' printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h @@ -31013,7 +31013,7 @@ # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by pdns-recursor $as_me 5.2.7, which was +This file was extended by pdns-recursor $as_me 5.2.8, which was generated by GNU Autoconf 2.71. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -31081,7 +31081,7 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\ -pdns-recursor config.status 5.2.7 +pdns-recursor config.status 5.2.8 configured by $0, generated by GNU Autoconf 2.71, with options \\"\$ac_cs_config\\" diff -Nru pdns-recursor-5.2.7/configure.ac pdns-recursor-5.2.8/configure.ac --- pdns-recursor-5.2.7/configure.ac 2025-11-25 13:41:40.000000000 +0000 +++ pdns-recursor-5.2.8/configure.ac 2026-01-21 11:12:59.000000000 +0000 @@ -1,6 +1,6 @@ AC_PREREQ([2.69]) -AC_INIT([pdns-recursor], [5.2.7]) +AC_INIT([pdns-recursor], [5.2.8]) 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-recursor-5.2.7/debian/changelog pdns-recursor-5.2.8/debian/changelog --- pdns-recursor-5.2.7/debian/changelog 2025-12-09 10:13:14.000000000 +0000 +++ pdns-recursor-5.2.8/debian/changelog 2026-02-11 22:05:53.000000000 +0000 @@ -1,3 +1,10 @@ +pdns-recursor (5.2.8-0+deb13u1) trixie-security; urgency=medium + + * New upstream version 5.2.8, fixing CVE-2026-24027 CVE-2026-0398 + (Closes: #1127490) + + -- Chris Hofstaedtler Wed, 11 Feb 2026 23:05:53 +0100 + pdns-recursor (5.2.7-0+deb13u1) trixie-security; urgency=medium * New upstream version 5.2.7, fixing CVE-2025-59030. diff -Nru pdns-recursor-5.2.7/effective_tld_names.dat pdns-recursor-5.2.8/effective_tld_names.dat --- pdns-recursor-5.2.7/effective_tld_names.dat 2025-11-25 13:42:30.000000000 +0000 +++ pdns-recursor-5.2.8/effective_tld_names.dat 2026-01-21 11:13:52.000000000 +0000 @@ -5,8 +5,8 @@ // Please pull this list from, and only from https://publicsuffix.org/list/public_suffix_list.dat, // rather than any other VCS sites. Pulling from any other URL is not guaranteed to be supported. -// VERSION: 2025-11-24_21-12-44_UTC -// COMMIT: 565e9dc7907cba1b3ae6b6d588120ec54d789806 +// VERSION: 2026-01-20_07-55-41_UTC +// COMMIT: d372af084b07ad6f6d26b3d63abeee1738456c09 // Instructions on pulling and using this list can be found at https://publicsuffix.org/list/. @@ -5667,7 +5667,9 @@ sj // sk : https://www.iana.org/domains/root/db/sk.html +// https://sk-nic.sk/ sk +org.sk // sl : http://www.nic.sl // Submitted by registry @@ -5688,7 +5690,6 @@ edu.sn gouv.sn org.sn -perso.sn univ.sn // so : http://sonic.so/policies/ @@ -6816,7 +6817,7 @@ // newGTLDs -// List of new gTLDs imported from https://www.icann.org/resources/registries/gtlds/v2/gtlds.json on 2025-11-08T15:16:38Z +// List of new gTLDs imported from https://www.icann.org/resources/registries/gtlds/v2/gtlds.json on 2026-01-08T15:25:34Z // This list is auto-generated, don't edit it manually. // aaa : American Automobile Association, Inc. // https://www.iana.org/domains/root/db/aaa.html @@ -10194,7 +10195,7 @@ // https://www.iana.org/domains/root/db/song.html song -// sony : Sony Corporation +// sony : Sony Group Corporation // https://www.iana.org/domains/root/db/sony.html sony @@ -10458,7 +10459,7 @@ // https://www.iana.org/domains/root/db/tools.html tools -// top : .TOP Registry +// top : Hong Kong Zhongze International Limited // https://www.iana.org/domains/root/db/top.html top @@ -11332,6 +11333,10 @@ // Submitted by Gavin Brown africa.com +// AgentbaseAI Inc. : https://assistant-ui.com +// Submitted by Simon Farshid +*.auiusercontent.com + // Agnat sp. z o.o. : https://domena.pl // Submitted by Przemyslaw Plewa beep.pl @@ -12331,6 +12336,10 @@ // concludes Amazon +// Antagonist B.V. : https://www.antagonist.nl/ +// Submitted by Sander Hoentjen +antagonist.cloud + // Apigee : https://apigee.com/ // Submitted by Apigee Security Team apigee.io @@ -12343,6 +12352,24 @@ // Submitted by Alexander Selivanov siiites.com +// Apple : https://www.apple.com +// Submitted by Apple DNS +int.apple +*.cloud.int.apple +*.r.cloud.int.apple +*.ap-north-1.r.cloud.int.apple +*.ap-south-1.r.cloud.int.apple +*.ap-south-2.r.cloud.int.apple +*.eu-central-1.r.cloud.int.apple +*.eu-north-1.r.cloud.int.apple +*.us-central-1.r.cloud.int.apple +*.us-central-2.r.cloud.int.apple +*.us-east-1.r.cloud.int.apple +*.us-east-2.r.cloud.int.apple +*.us-west-1.r.cloud.int.apple +*.us-west-2.r.cloud.int.apple +*.us-west-3.r.cloud.int.apple + // Appspace : https://www.appspace.com // Submitted by Appspace Security Team appspacehosted.com @@ -12355,6 +12382,7 @@ // Appwrite : https://appwrite.io // Submitted by Steven Nguyen appwrite.global +appwrite.network *.appwrite.run // Aptible : https://www.aptible.com/ @@ -12394,19 +12422,11 @@ // Submitted by Sam Smyth cdn.prod.atlassian-dev.net -// Authentick UG (haftungsbeschränkt) : https://authentick.net -// Submitted by Lukas Reschke -translated.page - // AVM : https://avm.de // Submitted by Andreas Weise myfritz.link myfritz.net -// AVStack Pte. Ltd. : https://avstack.io -// Submitted by Jasper Hugo -onavstack.net - // AW AdvisorWebsites.com Software Inc : https://advisorwebsites.com // Submitted by James Kennedy *.awdev.ca @@ -12502,6 +12522,8 @@ // Submitted by Andrea Brancaleoni brave.app *.s.brave.app +brave.dev +*.s.brave.dev brave.io *.s.brave.io @@ -12525,6 +12547,10 @@ cdn.bubble.io bubbleapps.io +// bwCloud-OS : https://bwcloud-os.de/ +// Submitted by Klara Mall +*.bwcloud-os-instance.de + // Bytemark Hosting : https://www.bytemark.co.uk // Submitted by Paul Cammish uk0.bigv.io @@ -12861,6 +12887,11 @@ deta.app deta.dev +// Deuxfleurs : https://deuxfleurs.fr +// Submitted by Aeddis Desauw +deuxfleurs.eu +deuxfleurs.page + // Developed Methods LLC : https://methods.dev // Submitted by Patrick Lorio *.at.ply.gg @@ -12910,8 +12941,11 @@ // Submitted by Calvin Browne jozi.biz -// DNSHE : https://de5.net +// DNSHE : https://www.dnshe.com // Submitted by DNSHE Team +ccwu.cc +cc.cd +us.ci de5.net // DNShome : https://www.dnshome.de/ @@ -12923,6 +12957,12 @@ online.th shop.th +// dotScot Domains : https://domains.scot/ +// Submitted by DNS Team +co.scot +me.scot +org.scot + // DrayTek Corp. : https://www.draytek.com/ // Submitted by Paul Fang drayddns.com @@ -13289,12 +13329,9 @@ // Emergent : https://emergent.sh // Submitted by Emergent Security Team emergent.cloud +preview.emergentagent.com emergent.host -// En root‽ : https://en-root.org -// Submitted by Emmanuel Raviart -en-root.fr - // Enalean SAS : https://www.enalean.com // Submitted by Enalean Security Team mytuleap.com @@ -13555,15 +13592,9 @@ flutterflow.app // fly.io : https://fly.io -// Submitted by Kurt Mackey +// Submitted by Kurt Mackey +sprites.app fly.dev -shw.io -edgeapp.net - -// Forgerock : https://www.forgerock.com -// Submitted by Roderick Parr -forgeblocks.com -id.forgerock.io // FoundryLabs, Inc : https://e2b.dev/ // Submitted by Jiri Sveceny @@ -13637,6 +13668,11 @@ *.kunden.ortsinfo.at *.statics.cloud +// Gadget Software Inc. : https://gadget.dev +// Submitted by Harry Brundage +gadget.app +gadget.host + // GCom Internet : https://www.gcom.net.au // Submitted by Leo Julius aliases121.com @@ -13871,6 +13907,10 @@ günstigbestellen.de günstigliefern.de +// GV.UY : https://nic.gv.uy +// Submitted by cheng +gv.uy + // Hackclub Nest : https://hackclub.app // Submitted by Cyteon hackclub.app @@ -13914,6 +13954,12 @@ // Submitted by David Grellscheid hepforge.org +// Hercules : https://hercules.app +// Submitted by Brendan Falk +onhercules.app +hercules-app.com +hercules-dev.com + // Heroku : https://www.heroku.com/ // Submitted by Shumon Huque herokuapp.com @@ -14003,6 +14049,10 @@ ibxos.it iliadboxos.it +// Imagine : https://imagine.dev +// Submitted by Steven Nguyen +imagine-proxy.work + // Incsub, LLC : https://incsub.com/ // Submitted by Aaron Edwards smushcdn.com @@ -14247,10 +14297,19 @@ ezproxy.kuleuven.be kuleuven.cloud +// Kevin Service : https://kevsrv.me +// Submitted by Kevin Service Team +ae.kg + // Keyweb AG : https://www.keyweb.de // Submitted by Martin Dannehl keymachine.de +// Kilo Code, Inc. : https://kilo.ai +// Submitted by Remon Oldenbeuving +kiloapps.ai +kiloapps.io + // KingHost : https://king.host // Submitted by Felipe Keller Braz kinghost.net @@ -14444,6 +14503,11 @@ // Submitted by Damien Tournoud *.magentosite.cloud +// Magic Patterns : https://www.magicpatterns.com +// Submitted by Teddy Ni +magicpatterns.app +magicpatternsapp.com + // Mail.Ru Group : https://hb.cldmail.ru // Submitted by Ilya Zaretskiy hb.cldmail.ru @@ -14538,8 +14602,12 @@ azurewebsites.net cloudapp.net trafficmanager.net +servicebus.usgovcloudapi.net +usgovcloudapp.net blob.core.windows.net servicebus.windows.net +azure-api.us +azurewebsites.us // MikroTik : https://mikrotik.com // Submitted by MikroTik SysAdmin Team @@ -14805,18 +14873,10 @@ nsupdate.info nerdpol.ovh -// NYC.mn : https://dot.nyc.mn/ -// Submitted by NYC.mn Subdomain Service -nyc.mn - // O3O.Foundation : https://o3o.foundation/ // Submitted by the prvcy.page Registry Team prvcy.page -// Obl.ong : https://obl.ong -// Submitted by Reese Armstrong -obl.ong - // Observable, Inc. : https://observablehq.com // Submitted by Mike Bostock observablehq.cloud @@ -14919,7 +14979,6 @@ // Submitted by Authgear Team & Skygear Developer authgear-staging.com authgearapps.com -skygearapp.com // OutSystems // Submitted by Duarte Santos @@ -14973,10 +15032,6 @@ gh.srv.us gl.srv.us -// PE Ulyanov Kirill Sergeevich : https://airy.host -// Submitted by Kirill Ulyanov -lk3.ru - // Peplink | Pepwave : http://peplink.com/ // Submitted by Steve Leung mypep.link @@ -14985,6 +15040,11 @@ // Submitted by Kenneth Van Alstyne perspecta.cloud +// Ping Identity : https://www.pingidentity.com +// Submitted by Ping Identity +forgeblocks.com +id.forgerock.io + // Plain : https://www.plain.com/ // Submitted by Jesús Hernández support.site @@ -15003,11 +15063,6 @@ *.platformsh.site *.tst.site -// Platter : https://platter.dev -// Submitted by Patrick Flor -platter-app.dev -platterp.us - // Pley AB : https://www.pley.com/ // Submitted by Henning Pohl pley.games @@ -15040,8 +15095,9 @@ priv.at // PROJECT ELIV : https://eliv.kr/ -// Submitted by PROJECT ELIV Domain Team +// Submitted by PROJECT ELIV DomainName Team c01.kr +eliv-api.kr eliv-cdn.kr eliv-dns.kr mmv.kr @@ -15055,6 +15111,10 @@ // Submitted by Martin Meier protonet.io +// PSL Sandbox : https://github.com/groundcat/PSL-Sandbox +// Submitted by groundcat +platter-app.dev + // PT Ekossistim Indo Digital : https://e.id // Submitted by Eid Team e.id @@ -15064,6 +15124,11 @@ chirurgiens-dentistes-en-france.fr byen.site +// PublicZone : https://publiczone.org/ +// Submitted by PublicZone NOC Team +nyc.mn +*.cn.st + // pubtls.org : https://www.pubtls.org // Submitted by Kor Nielsen pubtls.org @@ -15169,6 +15234,11 @@ // Submitted by Andrew Farries instances.spawn.cc +// Redpanda Data : https://redpanda.com +// Submitted by Infrastructure Team +*.clusters.rdpa.co +*.srvrless.rdpa.co + // Render : https://render.com // Submitted by Anurag Goel onrender.com @@ -15342,6 +15412,10 @@ // Submitted by Asheesh Laroia sandcats.io +// Sav.com, LLC : https://marketing.sav.com/ +// Submitted by Mukul Kudegave +sav.case + // SBE network solutions GmbH : https://www.sbe.de/ // Submitted by Norman Meilick logoip.com @@ -15554,6 +15628,10 @@ *.nxa.eu nx.gw +// Spawnbase : https://spawnbase.ai +// Submitted by Alexander Zuev +spawnbase.app + // SpeedPartner GmbH : https://www.speedpartner.de/ // Submitted by Stefan Neufeind customer.speedpartner.de @@ -15599,6 +15677,10 @@ stackit.run stackit.zone +// Stackryze : https://stackryze.com +// Submitted by Sudheer Bhuvana +indevs.in + // Staclar : https://staclar.com // Submitted by Q Misell // Submitted by Matthias Merkel @@ -15635,10 +15717,6 @@ // Submitted by Tony Schirmer storebase.store -// Storipress : https://storipress.com -// Submitted by Benno Liu -storipress.app - // Storj Labs Inc. : https://storj.io/ // Submitted by Philip Hutchins storj.farm @@ -15750,6 +15828,14 @@ site.tb-hosting.com directwp.eu +// TechEdge Limited: https://www.nic.uk.cc/ +// Submitted by TechEdge Developer +ec.cc +eu.cc +gu.cc +uk.cc +us.cc + // Teckids e.V. : https://www.teckids.org // Submitted by Dominik George edugit.io @@ -15835,22 +15921,6 @@ // Submitted by TuxFamily administrators tuxfamily.org -// TwoDNS : https://www.twodns.de/ -// Submitted by TwoDNS-Support -dd-dns.de -dray-dns.de -draydns.de -dyn-vpn.de -dynvpn.de -mein-vigor.de -my-vigor.de -my-wan.de -syno-ds.de -synology-diskstation.de -synology-ds.de -diskstation.eu -diskstation.org - // Typedream : https://typedream.com // Submitted by Putri Karunia typedream.app @@ -16040,6 +16110,8 @@ // Wix.com, Inc. : https://www.wix.com // Submitted by Shahar Talmi / Alon Kochba +base44.app +base44-sandbox.com wixsite.com wixstudio.com editorx.io @@ -16143,6 +16215,7 @@ // Zone.ID: https://zone.id // Submitted by Gx1.org zone.id +nett.to // ZoneABC : https://zoneabc.net // Submitted by ZoneABC Team diff -Nru pdns-recursor-5.2.7/lwres.cc pdns-recursor-5.2.8/lwres.cc --- pdns-recursor-5.2.7/lwres.cc 2025-11-25 13:24:50.000000000 +0000 +++ pdns-recursor-5.2.8/lwres.cc 2026-01-21 11:11:42.000000000 +0000 @@ -540,6 +540,8 @@ return ret; } + lwr->d_bytesReceived = len; + if (*chained) { auto msec = lwr->d_usec / 1000; if (msec > g_networkTimeoutMsec * 2 / 3) { diff -Nru pdns-recursor-5.2.7/lwres.hh pdns-recursor-5.2.8/lwres.hh --- pdns-recursor-5.2.7/lwres.hh 2025-11-25 13:24:50.000000000 +0000 +++ pdns-recursor-5.2.8/lwres.hh 2026-01-21 11:11:42.000000000 +0000 @@ -81,6 +81,7 @@ } vector d_records; + uint32_t d_bytesReceived{0}; int d_rcode{0}; bool d_validpacket{false}; bool d_aabit{false}, d_tcbit{false}; diff -Nru pdns-recursor-5.2.7/pdns_recursor.1 pdns-recursor-5.2.8/pdns_recursor.1 --- pdns-recursor-5.2.7/pdns_recursor.1 2025-11-25 13:42:29.000000000 +0000 +++ pdns-recursor-5.2.8/pdns_recursor.1 2026-01-21 11:13:52.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_RECURSOR" "1" "Nov 25, 2025" "" "PowerDNS Recursor" +.TH "PDNS_RECURSOR" "1" "Jan 21, 2026" "" "PowerDNS Recursor" .SH NAME pdns_recursor \- The PowerDNS Recursor binary .SH SYNOPSIS diff -Nru pdns-recursor-5.2.7/pdns_recursor.cc pdns-recursor-5.2.8/pdns_recursor.cc --- pdns-recursor-5.2.7/pdns_recursor.cc 2025-11-25 13:24:50.000000000 +0000 +++ pdns-recursor-5.2.8/pdns_recursor.cc 2026-01-21 11:11:42.000000000 +0000 @@ -1796,7 +1796,7 @@ #endif } - const bool intoPC = g_packetCache && !variableAnswer && !resolver.wasVariable(); + const bool intoPC = g_packetCache && !variableAnswer && !resolver.wasVariable() && (RecursorPacketCache::s_maxEntrySize == 0 || packet.size() <= RecursorPacketCache::s_maxEntrySize); 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, @@ -1928,6 +1928,7 @@ "answers", Logging::Loggable(ntohs(packetWriter.getHeader()->ancount)), "additional", Logging::Loggable(ntohs(packetWriter.getHeader()->arcount)), "outqueries", Logging::Loggable(resolver.d_outqueries), + "received", Logging::Loggable(resolver.d_bytesReceived), "netms", Logging::Loggable(resolver.d_totUsec / 1000.0), "totms", Logging::Loggable(static_cast(spentUsec) / 1000.0), "throttled", Logging::Loggable(resolver.d_throttledqueries), diff -Nru pdns-recursor-5.2.7/pubsuffix.cc pdns-recursor-5.2.8/pubsuffix.cc --- pdns-recursor-5.2.7/pubsuffix.cc 2025-11-25 13:42:30.000000000 +0000 +++ pdns-recursor-5.2.8/pubsuffix.cc 2026-01-21 11:13:53.000000000 +0000 @@ -4523,6 +4523,7 @@ "mil.sh", "net.sh", "org.sh", +"org.sk", "com.sl", "edu.sl", "gov.sl", @@ -4533,7 +4534,6 @@ "edu.sn", "gouv.sn", "org.sn", -"perso.sn", "univ.sn", "com.so", "edu.so", @@ -5969,13 +5969,16 @@ "transfer-webapp.cn-northwest-1.on.amazonwebservices.com.cn", "eero.online", "eero-stage.online", +"antagonist.cloud", "apigee.io", "panel.dev", "siiites.com", +"int.apple", "appspacehosted.com", "appspaceusercontent.com", "appudo.net", "appwrite.global", +"appwrite.network", "on-aptible.com", "f5.si", "arvanedge.ir", @@ -5989,10 +5992,8 @@ "sweetpepper.org", "myasustor.com", "cdn.prod.atlassian-dev.net", -"translated.page", "myfritz.link", "myfritz.net", -"onavstack.net", "ecommerce-shop.pl", "b-data.io", "balena-devices.com", @@ -6025,6 +6026,7 @@ "bplaced.net", "square7.net", "brave.app", +"brave.dev", "brave.io", "shop.brendly.ba", "shop.brendly.hr", @@ -6210,6 +6212,8 @@ "dedyn.io", "deta.app", "deta.dev", +"deuxfleurs.eu", +"deuxfleurs.page", "d6.ply.gg", "joinmc.link", "playit.plus", @@ -6229,10 +6233,16 @@ "discordsays.com", "discordsez.com", "jozi.biz", +"ccwu.cc", +"cc.cd", +"us.ci", "de5.net", "dnshome.de", "online.th", "shop.th", +"co.scot", +"me.scot", +"org.scot", "drayddns.com", "shoparena.pl", "dreamhosters.com", @@ -6549,8 +6559,8 @@ "elementor.cloud", "elementor.cool", "emergent.cloud", +"preview.emergentagent.com", "emergent.host", -"en-root.fr", "mytuleap.com", "tuleap-partners.com", "encr.app", @@ -6736,11 +6746,8 @@ "fldrv.com", "on-fleek.app", "flutterflow.app", +"sprites.app", "fly.dev", -"shw.io", -"edgeapp.net", -"forgeblocks.com", -"id.forgerock.io", "e2b.app", "framer.ai", "framer.app", @@ -6775,6 +6782,8 @@ "mydns.vc", "futurehosting.at", "futuremailing.at", +"gadget.app", +"gadget.host", "aliases121.com", "campaign.gov.uk", "service.gov.uk", @@ -6931,6 +6940,7 @@ "grafana-dev.net", "grayjayleagues.com", "grebedoc.dev", +"gv.uy", "hackclub.app", "hashbang.sh", "hasura.app", @@ -6947,6 +6957,9 @@ "helioho.st", "heliohost.us", "hepforge.org", +"onhercules.app", +"hercules-app.com", +"hercules-dev.com", "herokuapp.com", "heyflow.page", "heyflow.site", @@ -6982,6 +6995,7 @@ "iki.fi", "ibxos.it", "iliadboxos.it", +"imagine-proxy.work", "smushcdn.com", "wphostedmail.com", "wpmucdn.com", @@ -7139,7 +7153,10 @@ "kapsi.fi", "ezproxy.kuleuven.be", "kuleuven.cloud", +"ae.kg", "keymachine.de", +"kiloapps.ai", +"kiloapps.io", "kinghost.net", "uni5.net", "knightpoint.systems", @@ -7236,6 +7253,8 @@ "barsyonline.co.uk", "luyani.app", "luyani.net", +"magicpatterns.app", +"magicpatternsapp.com", "hb.cldmail.ru", "matlab.cloud", "modelscape.com", @@ -7283,8 +7302,12 @@ "azurewebsites.net", "cloudapp.net", "trafficmanager.net", +"servicebus.usgovcloudapi.net", +"usgovcloudapp.net", "blob.core.windows.net", "servicebus.windows.net", +"azure-api.us", +"azurewebsites.us", "routingthecloud.com", "sn.mynetname.net", "routingthecloud.net", @@ -7454,9 +7477,7 @@ "freeddns.us", "nsupdate.info", "nerdpol.ovh", -"nyc.mn", "prvcy.page", -"obl.ong", "observablehq.cloud", "static.observableusercontent.com", "omg.lol", @@ -7499,7 +7520,6 @@ "can.re", "authgear-staging.com", "authgearapps.com", -"skygearapp.com", "outsystemscloud.com", "ownprovider.com", "own.pm", @@ -7514,17 +7534,16 @@ "srv.us", "gh.srv.us", "gl.srv.us", -"lk3.ru", "mypep.link", "perspecta.cloud", +"forgeblocks.com", +"id.forgerock.io", "support.site", "on-web.fr", "upsunapp.com", "ent.platform.sh", "eu.platform.sh", "us.platform.sh", -"platter-app.dev", -"platterp.us", "pley.games", "onporter.run", "co.bn", @@ -7536,15 +7555,18 @@ "xen.prgmr.com", "priv.at", "c01.kr", +"eliv-api.kr", "eliv-cdn.kr", "eliv-dns.kr", "mmv.kr", "vki.kr", "dev.project-study.com", "protonet.io", +"platter-app.dev", "e.id", "chirurgiens-dentistes-en-france.fr", "byen.site", +"nyc.mn", "pubtls.org", "pythonanywhere.com", "eu.pythonanywhere.com", @@ -7677,6 +7699,7 @@ "from.tv", "sakura.tv", "sandcats.io", +"sav.case", "logoip.com", "logoip.de", "fr-par-1.baremetal.scw.cloud", @@ -7788,6 +7811,7 @@ "my.at", "my.de", "nx.gw", +"spawnbase.app", "customer.speedpartner.de", "myspreadshop.at", "myspreadshop.com.au", @@ -7818,6 +7842,7 @@ "stackit.rocks", "stackit.run", "stackit.zone", +"indevs.in", "musician.io", "novecore.site", "api.stdlib.com", @@ -7835,7 +7860,6 @@ "ipfs.storacha.link", "ipfs.w3s.link", "storebase.store", -"storipress.app", "storj.farm", "strapiapp.com", "media.strapiapp.com", @@ -7890,6 +7914,11 @@ "tche.br", "site.tb-hosting.com", "directwp.eu", +"ec.cc", +"eu.cc", +"gu.cc", +"uk.cc", +"us.cc", "edugit.io", "s3.teckids.org", "telebit.app", @@ -7933,19 +7962,6 @@ "site.transip.me", "tunnelmole.net", "tuxfamily.org", -"dd-dns.de", -"dray-dns.de", -"draydns.de", -"dyn-vpn.de", -"dynvpn.de", -"mein-vigor.de", -"my-vigor.de", -"my-wan.de", -"syno-ds.de", -"synology-diskstation.de", -"synology-ds.de", -"diskstation.eu", -"diskstation.org", "typedream.app", "pro.typeform.com", "uber.space", @@ -8014,6 +8030,8 @@ "windsurf.build", "panel.gg", "daemon.panel.gg", +"base44.app", +"base44-sandbox.com", "wixsite.com", "wixstudio.com", "editorx.io", @@ -8057,5 +8075,6 @@ "virtualserver.io", "enterprisecloud.nu", "zone.id", +"nett.to", "zabc.net", 0}; diff -Nru pdns-recursor-5.2.7/rec-main.cc pdns-recursor-5.2.8/rec-main.cc --- pdns-recursor-5.2.7/rec-main.cc 2025-11-25 13:24:50.000000000 +0000 +++ pdns-recursor-5.2.8/rec-main.cc 2026-01-21 11:11:42.000000000 +0000 @@ -1773,6 +1773,7 @@ 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_maxbytesperq = ::arg().asNum("max-bytesperq"); 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"); @@ -3311,6 +3312,8 @@ pdns::RecResolve::setInstanceParameters(arg()["server-id"], ttl, interval, selfResolveCheck, []() { reloadZoneConfiguration(g_yamlSettings); }); } + MemRecursorCache::s_maxEntrySize = ::arg().asNum("max-recordcache-entry-size"); + RecursorPacketCache::s_maxEntrySize = ::arg().asNum("max-packetcache-entry-size"); g_recCache = std::make_unique(::arg().asNum("record-cache-shards")); g_negCache = std::make_unique(::arg().asNum("record-cache-shards") / 8); if (!::arg().mustDo("disable-packetcache")) { diff -Nru pdns-recursor-5.2.7/rec_control.1 pdns-recursor-5.2.8/rec_control.1 --- pdns-recursor-5.2.7/rec_control.1 2025-11-25 13:42:29.000000000 +0000 +++ pdns-recursor-5.2.8/rec_control.1 2026-01-21 11:13:52.000000000 +0000 @@ -27,7 +27,7 @@ .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "REC_CONTROL" "1" "Nov 25, 2025" "" "PowerDNS Recursor" +.TH "REC_CONTROL" "1" "Jan 21, 2026" "" "PowerDNS Recursor" .SH NAME rec_control \- Command line tool to control a running Recursor .SH SYNOPSIS diff -Nru pdns-recursor-5.2.7/recpacketcache.cc pdns-recursor-5.2.8/recpacketcache.cc --- pdns-recursor-5.2.7/recpacketcache.cc 2025-11-25 13:24:50.000000000 +0000 +++ pdns-recursor-5.2.8/recpacketcache.cc 2026-01-21 11:11:42.000000000 +0000 @@ -11,6 +11,7 @@ #include "rec-taskqueue.hh" unsigned int RecursorPacketCache::s_refresh_ttlperc{0}; +uint32_t RecursorPacketCache::s_maxEntrySize{8192}; void RecursorPacketCache::setShardSizes(size_t shardSize) { diff -Nru pdns-recursor-5.2.7/recpacketcache.hh pdns-recursor-5.2.8/recpacketcache.hh --- pdns-recursor-5.2.7/recpacketcache.hh 2025-11-25 13:24:50.000000000 +0000 +++ pdns-recursor-5.2.8/recpacketcache.hh 2026-01-21 11:11:42.000000000 +0000 @@ -47,6 +47,7 @@ { public: static unsigned int s_refresh_ttlperc; + static uint32_t s_maxEntrySize; struct PBData { diff -Nru pdns-recursor-5.2.7/recursor_cache.cc pdns-recursor-5.2.8/recursor_cache.cc --- pdns-recursor-5.2.7/recursor_cache.cc 2025-11-25 13:24:50.000000000 +0000 +++ pdns-recursor-5.2.8/recursor_cache.cc 2026-01-21 11:11:42.000000000 +0000 @@ -75,6 +75,7 @@ uint16_t MemRecursorCache::s_maxServedStaleExtensions; uint16_t MemRecursorCache::s_maxRRSetSize = 256; bool MemRecursorCache::s_limitQTypeAny = true; +uint32_t MemRecursorCache::s_maxEntrySize = 8192; void MemRecursorCache::resetStaticsForTests() { @@ -84,6 +85,7 @@ SyncRes::s_minimumTTL = 0; s_maxRRSetSize = 256; s_limitQTypeAny = true; + s_maxEntrySize = 8192; } MemRecursorCache::MemRecursorCache(size_t mapsCount) : @@ -665,6 +667,7 @@ cacheEntry.d_tooBig = true; } cacheEntry.d_records.reserve(toStore); + size_t storeSize = sizeof(CacheEntry) + qname.getStorage().size(); // rough estimate 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. */ @@ -679,11 +682,15 @@ cacheEntry.d_orig_ttl = SyncRes::s_minimumTTL; } cacheEntry.d_records.push_back(record.getContent()); + storeSize += record.d_clen; // again, rough estimate if (--toStore == 0) { break; } } + if (s_maxEntrySize > 0 && storeSize > s_maxEntrySize) { + return; + } if (!isNew) { moveCacheItemToBack(lockedShard->d_map, stored); } diff -Nru pdns-recursor-5.2.7/recursor_cache.hh pdns-recursor-5.2.8/recursor_cache.hh --- pdns-recursor-5.2.7/recursor_cache.hh 2025-11-25 13:24:50.000000000 +0000 +++ pdns-recursor-5.2.8/recursor_cache.hh 2026-01-21 11:11:42.000000000 +0000 @@ -56,6 +56,7 @@ // but mark it as too big. Subsequent gets will cause an ImmediateServFailException to be thrown. static uint16_t s_maxRRSetSize; static bool s_limitQTypeAny; + static uint32_t s_maxEntrySize; [[nodiscard]] size_t size() const; [[nodiscard]] size_t bytes(); diff -Nru pdns-recursor-5.2.7/settings/cxxsettings-generated.cc pdns-recursor-5.2.8/settings/cxxsettings-generated.cc --- pdns-recursor-5.2.7/settings/cxxsettings-generated.cc 2025-11-25 13:42:30.000000000 +0000 +++ pdns-recursor-5.2.8/settings/cxxsettings-generated.cc 2026-01-21 11:13:53.000000000 +0000 @@ -98,13 +98,16 @@ ::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-entries", "If set, maximum number of entries in the main cache") = "1000000"; ::arg().set("max-cache-ttl", "maximum number of seconds to keep a cached entry in memory") = "86400"; + ::arg().set("max-recordcache-entry-size", "maximum storage size of a recordset stored in record cache") = "8192"; ::arg().set("max-concurrent-requests-per-tcp-connection", "Maximum number of requests handled concurrently per TCP connection") = "10"; ::arg().set("max-chain-length", "maximum number of queries that can be chained to an outgoing request, 0 is no limit") = "0"; ::arg().set("max-include-depth", "Maximum nested $INCLUDE depth when loading a zone from a file") = "20"; ::arg().set("max-generate-steps", "Maximum number of $GENERATE steps when loading a zone from a file") = "0"; ::arg().set("max-mthreads", "Maximum number of simultaneous Mtasker threads") = "2048"; ::arg().set("max-packetcache-entries", "maximum number of entries to keep in the packetcache") = "500000"; - ::arg().set("max-qperq", "Maximum outgoing queries per query") = "50"; + ::arg().set("max-packetcache-entry-size", "maximum size of a packet stored in the the packet cache") = "8192"; + ::arg().set("max-qperq", "Maximum outgoing queries per client query") = "50"; + ::arg().set("max-bytesperq", "Maximum number of received bytes per client query") = "100000"; ::arg().set("max-cnames-followed", "Maximum number CNAME records followed") = "10"; ::arg().setSwitch("limit-qtype-any", "Limit answers to ANY queries in size") = "yes"; ::arg().set("max-rrset-size", "Maximum size of RRSet in cache") = "256"; @@ -342,13 +345,16 @@ settings.recordcache.max_cache_bogus_ttl = static_cast(arg().asNum("max-cache-bogus-ttl")); settings.recordcache.max_entries = static_cast(arg().asNum("max-cache-entries")); settings.recordcache.max_ttl = static_cast(arg().asNum("max-cache-ttl")); + settings.recordcache.max_entry_size = static_cast(arg().asNum("max-recordcache-entry-size")); settings.incoming.max_concurrent_requests_per_tcp_connection = static_cast(arg().asNum("max-concurrent-requests-per-tcp-connection")); settings.recursor.max_chain_length = static_cast(arg().asNum("max-chain-length")); settings.recursor.max_include_depth = static_cast(arg().asNum("max-include-depth")); settings.recursor.max_generate_steps = static_cast(arg().asNum("max-generate-steps")); settings.recursor.max_mthreads = static_cast(arg().asNum("max-mthreads")); settings.packetcache.max_entries = static_cast(arg().asNum("max-packetcache-entries")); + settings.packetcache.max_entry_size = static_cast(arg().asNum("max-packetcache-entry-size")); settings.outgoing.max_qperq = static_cast(arg().asNum("max-qperq")); + settings.outgoing.max_bytesperq = static_cast(arg().asNum("max-bytesperq")); settings.recursor.max_cnames_followed = static_cast(arg().asNum("max-cnames-followed")); settings.recordcache.limit_qtype_any = arg().mustDo("limit-qtype-any"); settings.recordcache.max_rrset_size = static_cast(arg().asNum("max-rrset-size")); @@ -1083,6 +1089,13 @@ to_yaml(rustvalue.u64_val, value); return true; } + if (key == "max-recordcache-entry-size") { + section = "recordcache"; + fieldname = "max_entry_size"; + type_name = "u64"; + to_yaml(rustvalue.u64_val, value); + return true; + } if (key == "max-concurrent-requests-per-tcp-connection") { section = "incoming"; fieldname = "max_concurrent_requests_per_tcp_connection"; @@ -1125,6 +1138,13 @@ to_yaml(rustvalue.u64_val, value); return true; } + if (key == "max-packetcache-entry-size") { + section = "packetcache"; + fieldname = "max_entry_size"; + type_name = "u64"; + to_yaml(rustvalue.u64_val, value); + return true; + } if (key == "max-qperq") { section = "outgoing"; fieldname = "max_qperq"; @@ -1132,6 +1152,13 @@ to_yaml(rustvalue.u64_val, value); return true; } + if (key == "max-bytesperq") { + section = "outgoing"; + fieldname = "max_bytesperq"; + type_name = "u64"; + to_yaml(rustvalue.u64_val, value); + return true; + } if (key == "max-cnames-followed") { section = "recursor"; fieldname = "max_cnames_followed"; @@ -2050,13 +2077,16 @@ ::arg().set("max-cache-bogus-ttl") = to_arg(settings.recordcache.max_cache_bogus_ttl); ::arg().set("max-cache-entries") = to_arg(settings.recordcache.max_entries); ::arg().set("max-cache-ttl") = to_arg(settings.recordcache.max_ttl); + ::arg().set("max-recordcache-entry-size") = to_arg(settings.recordcache.max_entry_size); ::arg().set("max-concurrent-requests-per-tcp-connection") = to_arg(settings.incoming.max_concurrent_requests_per_tcp_connection); ::arg().set("max-chain-length") = to_arg(settings.recursor.max_chain_length); ::arg().set("max-include-depth") = to_arg(settings.recursor.max_include_depth); ::arg().set("max-generate-steps") = to_arg(settings.recursor.max_generate_steps); ::arg().set("max-mthreads") = to_arg(settings.recursor.max_mthreads); ::arg().set("max-packetcache-entries") = to_arg(settings.packetcache.max_entries); + ::arg().set("max-packetcache-entry-size") = to_arg(settings.packetcache.max_entry_size); ::arg().set("max-qperq") = to_arg(settings.outgoing.max_qperq); + ::arg().set("max-bytesperq") = to_arg(settings.outgoing.max_bytesperq); ::arg().set("max-cnames-followed") = to_arg(settings.recursor.max_cnames_followed); ::arg().set("limit-qtype-any") = to_arg(settings.recordcache.limit_qtype_any); ::arg().set("max-rrset-size") = to_arg(settings.recordcache.max_rrset_size); diff -Nru pdns-recursor-5.2.7/settings/rust/src/lib.rs pdns-recursor-5.2.8/settings/rust/src/lib.rs --- pdns-recursor-5.2.7/settings/rust/src/lib.rs 2025-11-25 13:42:30.000000000 +0000 +++ pdns-recursor-5.2.8/settings/rust/src/lib.rs 2026-01-21 11:13:53.000000000 +0000 @@ -936,6 +936,9 @@ #[serde(default = "crate::U64::<50>::value", skip_serializing_if = "crate::U64::<50>::is_equal")] max_qperq: u64, + #[serde(default = "crate::U64::<100000>::value", skip_serializing_if = "crate::U64::<100000>::is_equal")] + max_bytesperq: u64, + #[serde(default = "crate::U64::<10>::value", skip_serializing_if = "crate::U64::<10>::is_equal")] max_ns_address_qperq: u64, @@ -1003,6 +1006,9 @@ #[serde(default = "crate::U64::<500000>::value", skip_serializing_if = "crate::U64::<500000>::is_equal")] max_entries: u64, + #[serde(default = "crate::U64::<8192>::value", skip_serializing_if = "crate::U64::<8192>::is_equal")] + max_entry_size: u64, + #[serde(default = "crate::U64::<86400>::value", skip_serializing_if = "crate::U64::<86400>::is_equal")] ttl: u64, @@ -1129,6 +1135,9 @@ #[serde(default = "crate::U64::<86400>::value", skip_serializing_if = "crate::U64::<86400>::is_equal")] max_ttl: u64, + #[serde(default = "crate::U64::<8192>::value", skip_serializing_if = "crate::U64::<8192>::is_equal")] + max_entry_size: u64, + #[serde(default = "crate::Bool::::value", skip_serializing_if = "crate::if_true")] limit_qtype_any: bool, @@ -2063,6 +2072,9 @@ if m.contains_key("max_qperq") { rhs.max_qperq.clone_into(&mut self.max_qperq); } + if m.contains_key("max_bytesperq") { + rhs.max_bytesperq.clone_into(&mut self.max_bytesperq); + } if m.contains_key("max_ns_address_qperq") { rhs.max_ns_address_qperq.clone_into(&mut self.max_ns_address_qperq); } @@ -2136,6 +2148,9 @@ if m.contains_key("max_entries") { rhs.max_entries.clone_into(&mut self.max_entries); } + if m.contains_key("max_entry_size") { + rhs.max_entry_size.clone_into(&mut self.max_entry_size); + } if m.contains_key("ttl") { rhs.ttl.clone_into(&mut self.ttl); } @@ -2277,6 +2292,9 @@ if m.contains_key("max_ttl") { rhs.max_ttl.clone_into(&mut self.max_ttl); } + if m.contains_key("max_entry_size") { + rhs.max_entry_size.clone_into(&mut self.max_entry_size); + } if m.contains_key("limit_qtype_any") { rhs.limit_qtype_any.clone_into(&mut self.limit_qtype_any); } diff -Nru pdns-recursor-5.2.7/settings/table.py pdns-recursor-5.2.8/settings/table.py --- pdns-recursor-5.2.7/settings/table.py 2025-11-25 13:24:50.000000000 +0000 +++ pdns-recursor-5.2.8/settings/table.py 2026-01-21 11:11:42.000000000 +0000 @@ -1501,6 +1501,19 @@ '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_entry_size', + 'section' : 'recordcache', + 'oldname': 'max-recordcache-entry-size', + 'type' : LType.Uint64, + 'default' : '8192', + 'help' : 'maximum storage size of a recordset stored in record cache', + 'doc' : ''' +Maximum size of storage used by a single record cache entry. Entries larger than this number will not be stored. +Zero means no limit. +''', + 'versionadded': ['5.1.10', '5.2.8', '5.3.5', '5.4.0'], + }, + { 'name' : 'max_concurrent_requests_per_tcp_connection', 'section' : 'incoming', 'type' : LType.Uint64, @@ -1577,11 +1590,24 @@ 'runtime': 'set-max-packetcache-entries', }, { + 'name' : 'max_entry_size', + 'section' : 'packetcache', + 'oldname' : 'max-packetcache-entry-size', + 'type' : LType.Uint64, + 'default' : '8192', + 'help' : 'maximum size of a packet stored in the the packet cache', + 'doc' : ''' +Maximum size of packets stored in the packet cache. Packets larger than this number will not be stored. +Zero means no limit. +''', + 'versionadded': ['5.1.10', '5.2.8', '5.3.5', '5.4.0'], + }, + { 'name' : 'max_qperq', 'section' : 'outgoing', 'type' : LType.Uint64, 'default' : '50', - 'help' : 'Maximum outgoing queries per query', + 'help' : 'Maximum outgoing queries per client 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. @@ -1589,6 +1615,18 @@ '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_bytesperq', + 'section' : 'outgoing', + 'type' : LType.Uint64, + 'default' : '100000', + 'help' : 'Maximum number of received bytes per client query', + 'doc' : ''' +The maximum number of cumulative bytes that will be accepted during the resolution of a single client query. +This is useful to limit amplification attacks. + ''', + 'versionadded': '5.4.0', + }, + { 'name' : 'max_cnames_followed', 'section' : 'recursor', 'type' : LType.Uint64, diff -Nru pdns-recursor-5.2.7/syncres.cc pdns-recursor-5.2.8/syncres.cc --- pdns-recursor-5.2.7/syncres.cc 2025-11-25 13:24:50.000000000 +0000 +++ pdns-recursor-5.2.8/syncres.cc 2026-01-21 11:11:42.000000000 +0000 @@ -464,6 +464,7 @@ unsigned int SyncRes::s_maxbogusttl; unsigned int SyncRes::s_maxcachettl; unsigned int SyncRes::s_maxqperq; +unsigned int SyncRes::s_maxbytesperq; unsigned int SyncRes::s_maxnsperresolve; unsigned int SyncRes::s_maxnsaddressqperq; unsigned int SyncRes::s_maxtotusec; @@ -573,7 +574,7 @@ } 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_authzonequeries(0), d_outqueries(0), d_tcpoutqueries(0), d_dotoutqueries(0), d_throttledqueries(0), d_timeouts(0), d_unreachables(0), d_bytesReceived(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::max(); } @@ -3569,7 +3570,10 @@ 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()); + throw ImmediateServFailException("More than " + std::to_string(s_maxqperq) + " (outgoing.max_qperq) queries sent or throttled while resolving " + qname.toLogString()); + } + if (d_bytesReceived > s_maxbytesperq) { + throw ImmediateServFailException("More than " + std::to_string(s_maxbytesperq) + " (outgoing.max_bytesperq) bytes received while resolving " + qname.toLogString()); } } @@ -4278,6 +4282,25 @@ return qtype == QType::CNAME || qtype == QType::DNAME; } +// Walk the chain from qname, only adding names that can be reached +static std::unordered_set sanitizeCNAMEChain(const DNSName& qname, std::unordered_map& cnameChain) +{ + std::unordered_set allowed = {qname}; + DNSName key{qname}; + while (true) { + if (auto probe = cnameChain.find(key); probe != cnameChain.end()) { + allowed.emplace(probe->second); + key = probe->second; + // This will prevent looping in this function. CNAME loops themselves we handle higher up, see handleNewTarget() + cnameChain.erase(probe); + } + else { + break; + } + } + return allowed; +} + 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; @@ -4285,6 +4308,7 @@ to remain */ std::unordered_set allowedAdditionals = {qname}; std::unordered_set allowedAnswerNames = {qname}; + std::unordered_map cnameChain; bool cnameSeen = false; bool haveAnswers = false; bool acceptDelegation = false; @@ -4359,7 +4383,7 @@ haveAnswers = true; if (rec->d_type == QType::CNAME) { if (auto cnametarget = getRR(*rec); cnametarget != nullptr) { - allowedAnswerNames.insert(cnametarget->getTarget()); + cnameChain.emplace(rec->d_name, cnametarget->getTarget()); } cnameSeen = cnameSeen || qname == rec->d_name; } @@ -4423,6 +4447,10 @@ acceptDelegation = true; } + if (cnameChain.size() > 0) { + auto allowed = sanitizeCNAMEChain(qname, cnameChain); + allowedAnswerNames.insert(allowed.begin(), allowed.end()); + } sanitizeRecordsPass2(prefix, lwr, qname, qtype, auth, allowedAnswerNames, allowedAdditionals, cnameSeen, acceptDelegation && !soaInAuth, skipvec, skipCount); } @@ -5564,6 +5592,7 @@ throw ImmediateServFailException("Query killed by policy"); } + d_bytesReceived += lwr.d_bytesReceived; d_totUsec += lwr.d_usec; if (resolveret == LWResult::Result::Spoofed) { @@ -5883,6 +5912,13 @@ nameservers.insert({nameserver, {{}, false}}); } LOG("looping to them" << endl); + if (s_maxnsperresolve > 0 && nameservers.size() > s_maxnsperresolve) { + LOG(prefix << qname << "Reducing number of NS we are willing to consider to " << s_maxnsperresolve << endl); + NsSet selected; + std::sample(nameservers.cbegin(), nameservers.cend(), std::inserter(selected, selected.begin()), s_maxnsperresolve, pdns::dns_random_engine()); + nameservers = std::move(selected); + } + *gotNewServers = true; auth = std::move(newauth); @@ -5936,11 +5972,17 @@ if (rnameservers.size() > nsLimit) { int newLimit = static_cast(nsLimit - (rnameservers.size() - nsLimit)); nsLimit = std::max(5, newLimit); + LOG("Applying nsLimit " << nsLimit << endl); } + // If multiple NS records resolve to the same IP, we don't want to ask again, so keep track + std::set visitedAddresses; 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()); + throw ImmediateServFailException(std::to_string(nsLimit) + " (outgoing.max_ns_address_qperq) or more queries with empty results for NS addresses sent resolving " + qname.toLogString()); + } + if (s_maxnsperresolve > 0 && visitedAddresses.size() > 2 * s_maxnsperresolve) { + throw ImmediateServFailException("More than " + std::to_string(2 * s_maxnsperresolve) + " (2 * outgoing.max_ns_per_resolve) identical queries sent to auth IPs 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); @@ -6038,6 +6080,11 @@ } for (remoteIP = remoteIPs.begin(); remoteIP != remoteIPs.end(); ++remoteIP) { + auto inserted = visitedAddresses.insert(*remoteIP).second; + if (!wasForwarded && !inserted) { + LOG(prefix << qname << ": Already visited " << remoteIP->toStringWithPort() << ", asking '" << qname << "|" << qtype << "'; skipping" << endl); + continue; + } LOG(prefix << qname << ": Trying IP " << remoteIP->toStringWithPort() << ", asking '" << qname << "|" << qtype << "'" << endl); if (throttledOrBlocked(prefix, *remoteIP, qname, qtype, pierceDontQuery)) { diff -Nru pdns-recursor-5.2.7/syncres.hh pdns-recursor-5.2.8/syncres.hh --- pdns-recursor-5.2.7/syncres.hh 2025-11-25 13:24:50.000000000 +0000 +++ pdns-recursor-5.2.8/syncres.hh 2026-01-21 11:11:42.000000000 +0000 @@ -525,6 +525,7 @@ static unsigned int s_minimumTTL; static unsigned int s_minimumECSTTL; static unsigned int s_maxqperq; + static unsigned int s_maxbytesperq; static unsigned int s_maxnsperresolve; static unsigned int s_maxnsaddressqperq; static unsigned int s_maxtotusec; @@ -589,9 +590,10 @@ unsigned int d_throttledqueries; unsigned int d_timeouts; unsigned int d_unreachables; + unsigned int d_bytesReceived; unsigned int d_totUsec; unsigned int d_maxdepth{0}; - // Initialized ony once, as opposed to d_now which gets updated after outgoing requests + // Initialized only once, as opposed to d_now which gets updated after outgoing requests struct timeval d_fixednow; private: diff -Nru pdns-recursor-5.2.7/test-recursorcache_cc.cc pdns-recursor-5.2.8/test-recursorcache_cc.cc --- pdns-recursor-5.2.7/test-recursorcache_cc.cc 2025-11-25 13:24:50.000000000 +0000 +++ pdns-recursor-5.2.8/test-recursorcache_cc.cc 2026-01-21 11:11:42.000000000 +0000 @@ -394,6 +394,7 @@ BOOST_AUTO_TEST_CASE(test_RecursorCacheBig) { MemRecursorCache::resetStaticsForTests(); + MemRecursorCache::s_maxEntrySize = 64 * 1024; MemRecursorCache MRC; std::vector records; diff -Nru pdns-recursor-5.2.7/test-syncres_cc.cc pdns-recursor-5.2.8/test-syncres_cc.cc --- pdns-recursor-5.2.7/test-syncres_cc.cc 2025-11-25 13:24:50.000000000 +0000 +++ pdns-recursor-5.2.8/test-syncres_cc.cc 2026-01-21 11:11:42.000000000 +0000 @@ -149,6 +149,7 @@ SyncRes::s_maxqperq = 50; SyncRes::s_maxnsaddressqperq = 10; + SyncRes::s_maxbytesperq = 100000; SyncRes::s_maxtotusec = 1000 * 7000; SyncRes::s_maxdepth = 40; SyncRes::s_maxnegttl = 3600; diff -Nru pdns-recursor-5.2.7/test-syncres_cc1.cc pdns-recursor-5.2.8/test-syncres_cc1.cc --- pdns-recursor-5.2.7/test-syncres_cc1.cc 2025-11-25 13:24:50.000000000 +0000 +++ pdns-recursor-5.2.8/test-syncres_cc1.cc 2026-01-21 11:11:42.000000000 +0000 @@ -358,6 +358,50 @@ } } +BOOST_AUTO_TEST_CASE(test_edns_formerr_but_edns_enabled_limit_bytes) +{ + std::unique_ptr sr; + initSR(sr); + + /* in this test, the auth answers with FormErr to an EDNS-enabled + query, but the response does contain EDNS so we should not mark + it as EDNS ignorant or intolerant. + + We are MISUING this test to test max_bytesperq limit + */ + size_t queriesWithEDNS = 0; + size_t queriesWithoutEDNS = 0; + std::set usedServers; + + sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& /* domain */, int type, bool /* doTCP */, bool /* sendRDQuery */, int EDNS0Level, struct timeval* /* now */, boost::optional& /* srcmask */, const ResolveContext& /* context */, LWResult* res, bool* /* chained */) { + if (EDNS0Level > 0) { + queriesWithEDNS++; + } + else { + queriesWithoutEDNS++; + } + usedServers.insert(address); + + if (type == QType::DNAME) { + setLWResult(res, RCode::FormErr); + if (EDNS0Level > 0) { + res->d_haveEDNS = true; + } + res->d_bytesReceived = 10000; + return LWResult::Result::Success; + } + + return LWResult::Result::Timeout; + }); + + primeHints(); + + vector ret; + BOOST_CHECK_EXCEPTION(sr->beginResolve(DNSName("powerdns.com."), QType(QType::DNAME), QClass::IN, ret), ImmediateServFailException, [&](const ImmediateServFailException& isfe) { + return isfe.reason.substr(0, 9) == "More than"; + }); +} + BOOST_AUTO_TEST_CASE(test_meta_types) { std::unique_ptr sr; @@ -771,6 +815,58 @@ } } +BOOST_AUTO_TEST_CASE(test_forward_ns_send_servfail_same) +{ + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + std::set downServers; + size_t queriesCount = 0; + + const DNSName target("www.refused."); + + SyncRes::AuthDomain ad; + // It is documented that having duplicate forwarders should result into a retry on servfail + const std::vector forwardedNSs{ComboAddress("192.0.2.42:53"), ComboAddress("192.0.2.42:53")}; + ad.d_rdForward = false; + ad.d_servers = forwardedNSs; + (*SyncRes::t_sstorage.domainmap)[DNSName("refused.")] = ad; + + sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& /* domain */, int /* type */, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional& /* 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); + addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL, 3600); + return LWResult::Result::Success; + } + + ++queriesCount; + downServers.insert(address); + + setLWResult(res, RCode::ServFail, false, false, true); + + return LWResult::Result::Success; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::ServFail); + BOOST_CHECK_EQUAL(ret.size(), 0U); + BOOST_CHECK_EQUAL(downServers.size(), 1U); + BOOST_CHECK_EQUAL(queriesCount, 2U); + + const auto& server = forwardedNSs.at(0); + BOOST_CHECK_EQUAL(downServers.count(server), 1U); + /* on servfail from a server we forward to we only increase the NS speed so + that a different server might be tried instead, but we don't throttle */ + BOOST_CHECK(!SyncRes::isThrottled(time(nullptr), server, target, QType::A)); + BOOST_CHECK_EQUAL(SyncRes::getNSSpeed(DNSName(server.toStringWithPort()), server), 1000000U); + BOOST_CHECK_EQUAL(SyncRes::getEDNSStatus(server), SyncRes::EDNSStatus::EDNSOK); +} + BOOST_AUTO_TEST_CASE(test_only_one_ns_up_resolving_itself_with_glue) { std::unique_ptr sr; @@ -2075,6 +2171,69 @@ BOOST_CHECK_EQUAL(ret[0].d_name, target); } +BOOST_AUTO_TEST_CASE(test_broken_cname_chain) +{ + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + const DNSName target("www.powerdns.com."); + const DNSName subtarget("sub.www.powerdns.com."); + const DNSName subns("new-sub.www.powerdns.com."); + const DNSName unrelated("unrelated.com."); + + timeval now{}; + Utility::gettimeofday(&now, nullptr); + + sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& domain, int qtype, bool /* doTCP */, bool /* sendRDQuery */, int /* EDNS0Level */, struct timeval* /* now */, boost::optional& /* 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; + } + if (address == ComboAddress("192.0.2.1:53")) { + if (domain == target) { + if (qtype == QType::NS) { + setLWResult(res, 0, true, false, false); + addRecordToLW(res, target, QType::NS, target.toString(), DNSResourceRecord::ANSWER); + addRecordToLW(res, subtarget, QType::NS, subns.toString(), DNSResourceRecord::ANSWER); + addRecordToLW(res, unrelated, QType::NS, subns.toString(), DNSResourceRecord::ANSWER); + addRecordToLW(res, subtarget, QType::NS, subtarget.toString(), DNSResourceRecord::ANSWER); + addRecordToLW(res, subtarget, QType::CNAME, subtarget.toString(), DNSResourceRecord::ANSWER); + return LWResult::Result::Success; + } + if (qtype == QType::A) { + setLWResult(res, 0, true, false, false); + return LWResult::Result::Success; + } + } + } + + return LWResult::Result::Timeout; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(ret.size(), 1U); + + time_t cached = g_recCache->get(now.tv_sec, subtarget, QType::NS, MemRecursorCache::None, &ret, ComboAddress()); + BOOST_CHECK(cached <= 0); + cached = g_recCache->get(now.tv_sec, unrelated, QType::NS, MemRecursorCache::None, &ret, ComboAddress()); + BOOST_CHECK(cached <= 0); + cached = g_recCache->get(now.tv_sec, subtarget, QType::CNAME, MemRecursorCache::None, &ret, ComboAddress()); + BOOST_CHECK(cached <= 0); + + // And again to check cache + ret.clear(); + res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(ret.size(), 1U); +} + BOOST_AUTO_TEST_CASE(test_time_limit) { std::unique_ptr sr; diff -Nru pdns-recursor-5.2.7/test-syncres_cc2.cc pdns-recursor-5.2.8/test-syncres_cc2.cc --- pdns-recursor-5.2.7/test-syncres_cc2.cc 2025-11-25 13:24:50.000000000 +0000 +++ pdns-recursor-5.2.8/test-syncres_cc2.cc 2026-01-21 11:11:42.000000000 +0000 @@ -1546,9 +1546,9 @@ } catch (const ImmediateServFailException& ex) { BOOST_CHECK_EQUAL(ret.size(), 0U); - // one query to get NSs, then A and AAAA for each NS, 5th NS hits the limit - // limit is reduced to 5, because zone publishes many (20) NS - BOOST_CHECK_EQUAL(queriesCount, 11U); + // one query to get NSs, then A and AAAA for each NS, 7th NS hits the limit + // limit is reduced to 7, because zone publishes many (20, but actually reduced to 13 used) NS + BOOST_CHECK_EQUAL(queriesCount, 15U); } }