Skip to content

Commit 15e4956

Browse files
authored
Merge pull request #16841 from omoerbeek/rec-5.2.8-to-be
Backport to 5.2.x: SA 2026-01 including build fixes for man pages and docs
2 parents 54039c9 + ba28ea0 commit 15e4956

15 files changed

Lines changed: 275 additions & 10 deletions

pdns/recursordist/lwres.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,8 @@ static LWResult::Result asyncresolve(const ComboAddress& address, const DNSName&
540540
return ret;
541541
}
542542

543+
lwr->d_bytesReceived = len;
544+
543545
if (*chained) {
544546
auto msec = lwr->d_usec / 1000;
545547
if (msec > g_networkTimeoutMsec * 2 / 3) {

pdns/recursordist/lwres.hh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ public:
8181
}
8282

8383
vector<DNSRecord> d_records;
84+
uint32_t d_bytesReceived{0};
8485
int d_rcode{0};
8586
bool d_validpacket{false};
8687
bool d_aabit{false}, d_tcbit{false};

pdns/recursordist/pdns_recursor.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1796,7 +1796,7 @@ void startDoResolve(void* arg) // NOLINT(readability-function-cognitive-complexi
17961796
#endif
17971797
}
17981798

1799-
const bool intoPC = g_packetCache && !variableAnswer && !resolver.wasVariable();
1799+
const bool intoPC = g_packetCache && !variableAnswer && !resolver.wasVariable() && (RecursorPacketCache::s_maxEntrySize == 0 || packet.size() <= RecursorPacketCache::s_maxEntrySize);
18001800
if (intoPC) {
18011801
minTTL = capPacketCacheTTL(*packetWriter.getHeader(), minTTL, seenAuthSOA);
18021802
g_packetCache->insertResponsePacket(comboWriter->d_tag, comboWriter->d_qhash, std::move(comboWriter->d_query), comboWriter->d_mdp.d_qname,
@@ -1928,6 +1928,7 @@ void startDoResolve(void* arg) // NOLINT(readability-function-cognitive-complexi
19281928
"answers", Logging::Loggable(ntohs(packetWriter.getHeader()->ancount)),
19291929
"additional", Logging::Loggable(ntohs(packetWriter.getHeader()->arcount)),
19301930
"outqueries", Logging::Loggable(resolver.d_outqueries),
1931+
"received", Logging::Loggable(resolver.d_bytesReceived),
19311932
"netms", Logging::Loggable(resolver.d_totUsec / 1000.0),
19321933
"totms", Logging::Loggable(static_cast<double>(spentUsec) / 1000.0),
19331934
"throttled", Logging::Loggable(resolver.d_throttledqueries),

pdns/recursordist/rec-main.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1773,6 +1773,7 @@ static int initSyncRes(Logr::log_t log)
17731773
SyncRes::s_serverID = ::arg()["server-id"];
17741774
// This bound is dynamically adjusted in SyncRes, depending on qname minimization being active
17751775
SyncRes::s_maxqperq = ::arg().asNum("max-qperq");
1776+
SyncRes::s_maxbytesperq = ::arg().asNum("max-bytesperq");
17761777
SyncRes::s_maxnsperresolve = ::arg().asNum("max-ns-per-resolve");
17771778
SyncRes::s_maxnsaddressqperq = ::arg().asNum("max-ns-address-qperq");
17781779
SyncRes::s_maxtotusec = 1000 * ::arg().asNum("max-total-msec");
@@ -3311,6 +3312,8 @@ int main(int argc, char** argv)
33113312
pdns::RecResolve::setInstanceParameters(arg()["server-id"], ttl, interval, selfResolveCheck, []() { reloadZoneConfiguration(g_yamlSettings); });
33123313
}
33133314

3315+
MemRecursorCache::s_maxEntrySize = ::arg().asNum("max-recordcache-entry-size");
3316+
RecursorPacketCache::s_maxEntrySize = ::arg().asNum("max-packetcache-entry-size");
33143317
g_recCache = std::make_unique<MemRecursorCache>(::arg().asNum("record-cache-shards"));
33153318
g_negCache = std::make_unique<NegCache>(::arg().asNum("record-cache-shards") / 8);
33163319
if (!::arg().mustDo("disable-packetcache")) {

pdns/recursordist/recpacketcache.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "rec-taskqueue.hh"
1212

1313
unsigned int RecursorPacketCache::s_refresh_ttlperc{0};
14+
uint32_t RecursorPacketCache::s_maxEntrySize{8192};
1415

1516
void RecursorPacketCache::setShardSizes(size_t shardSize)
1617
{

pdns/recursordist/recpacketcache.hh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ class RecursorPacketCache : public PacketCache
4747
{
4848
public:
4949
static unsigned int s_refresh_ttlperc;
50+
static uint32_t s_maxEntrySize;
5051

5152
struct PBData
5253
{

pdns/recursordist/recursor_cache.cc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
uint16_t MemRecursorCache::s_maxServedStaleExtensions;
7676
uint16_t MemRecursorCache::s_maxRRSetSize = 256;
7777
bool MemRecursorCache::s_limitQTypeAny = true;
78+
uint32_t MemRecursorCache::s_maxEntrySize = 8192;
7879

7980
void MemRecursorCache::resetStaticsForTests()
8081
{
@@ -84,6 +85,7 @@ void MemRecursorCache::resetStaticsForTests()
8485
SyncRes::s_minimumTTL = 0;
8586
s_maxRRSetSize = 256;
8687
s_limitQTypeAny = true;
88+
s_maxEntrySize = 8192;
8789
}
8890

8991
MemRecursorCache::MemRecursorCache(size_t mapsCount) :
@@ -665,6 +667,7 @@ void MemRecursorCache::replace(time_t now, const DNSName& qname, const QType qty
665667
cacheEntry.d_tooBig = true;
666668
}
667669
cacheEntry.d_records.reserve(toStore);
670+
size_t storeSize = sizeof(CacheEntry) + qname.getStorage().size(); // rough estimate
668671
for (const auto& record : content) {
669672
/* Yes, we have altered the d_ttl value by adding time(nullptr) to it
670673
prior to calling this function, so the TTL actually holds a TTD. */
@@ -679,11 +682,15 @@ void MemRecursorCache::replace(time_t now, const DNSName& qname, const QType qty
679682
cacheEntry.d_orig_ttl = SyncRes::s_minimumTTL;
680683
}
681684
cacheEntry.d_records.push_back(record.getContent());
685+
storeSize += record.d_clen; // again, rough estimate
682686
if (--toStore == 0) {
683687
break;
684688
}
685689
}
686690

691+
if (s_maxEntrySize > 0 && storeSize > s_maxEntrySize) {
692+
return;
693+
}
687694
if (!isNew) {
688695
moveCacheItemToBack<SequencedTag>(lockedShard->d_map, stored);
689696
}

pdns/recursordist/recursor_cache.hh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ public:
5656
// but mark it as too big. Subsequent gets will cause an ImmediateServFailException to be thrown.
5757
static uint16_t s_maxRRSetSize;
5858
static bool s_limitQTypeAny;
59+
static uint32_t s_maxEntrySize;
5960

6061
[[nodiscard]] size_t size() const;
6162
[[nodiscard]] size_t bytes();

pdns/recursordist/settings/table.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1500,6 +1500,19 @@
15001500
''',
15011501
'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.')
15021502
},
1503+
{
1504+
'name' : 'max_entry_size',
1505+
'section' : 'recordcache',
1506+
'oldname': 'max-recordcache-entry-size',
1507+
'type' : LType.Uint64,
1508+
'default' : '8192',
1509+
'help' : 'maximum storage size of a recordset stored in record cache',
1510+
'doc' : '''
1511+
Maximum size of storage used by a single record cache entry. Entries larger than this number will not be stored.
1512+
Zero means no limit.
1513+
''',
1514+
'versionadded': ['5.1.10', '5.2.8', '5.3.5', '5.4.0'],
1515+
},
15031516
{
15041517
'name' : 'max_concurrent_requests_per_tcp_connection',
15051518
'section' : 'incoming',
@@ -1576,18 +1589,43 @@
15761589
''',
15771590
'runtime': 'set-max-packetcache-entries',
15781591
},
1592+
{
1593+
'name' : 'max_entry_size',
1594+
'section' : 'packetcache',
1595+
'oldname' : 'max-packetcache-entry-size',
1596+
'type' : LType.Uint64,
1597+
'default' : '8192',
1598+
'help' : 'maximum size of a packet stored in the the packet cache',
1599+
'doc' : '''
1600+
Maximum size of packets stored in the packet cache. Packets larger than this number will not be stored.
1601+
Zero means no limit.
1602+
''',
1603+
'versionadded': ['5.1.10', '5.2.8', '5.3.5', '5.4.0'],
1604+
},
15791605
{
15801606
'name' : 'max_qperq',
15811607
'section' : 'outgoing',
15821608
'type' : LType.Uint64,
15831609
'default' : '50',
1584-
'help' : 'Maximum outgoing queries per query',
1610+
'help' : 'Maximum outgoing queries per client query',
15851611
'doc' : '''
15861612
The maximum number of outgoing queries that will be sent out during the resolution of a single client query.
15871613
This is used to avoid cycles resolving names.
15881614
''',
15891615
'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.'),
15901616
},
1617+
{
1618+
'name' : 'max_bytesperq',
1619+
'section' : 'outgoing',
1620+
'type' : LType.Uint64,
1621+
'default' : '100000',
1622+
'help' : 'Maximum number of received bytes per client query',
1623+
'doc' : '''
1624+
The maximum number of cumulative bytes that will be accepted during the resolution of a single client query.
1625+
This is useful to limit amplification attacks.
1626+
''',
1627+
'versionadded': '5.4.0',
1628+
},
15911629
{
15921630
'name' : 'max_cnames_followed',
15931631
'section' : 'recursor',

pdns/recursordist/syncres.cc

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,7 @@ unsigned int SyncRes::s_maxnegttl;
464464
unsigned int SyncRes::s_maxbogusttl;
465465
unsigned int SyncRes::s_maxcachettl;
466466
unsigned int SyncRes::s_maxqperq;
467+
unsigned int SyncRes::s_maxbytesperq;
467468
unsigned int SyncRes::s_maxnsperresolve;
468469
unsigned int SyncRes::s_maxnsaddressqperq;
469470
unsigned int SyncRes::s_maxtotusec;
@@ -573,7 +574,7 @@ static inline void accountAuthLatency(uint64_t usec, int family)
573574
}
574575

575576
SyncRes::SyncRes(const struct timeval& now) :
576-
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)
577+
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)
577578
{
578579
d_validationContext.d_nsec3IterationsRemainingQuota = s_maxnsec3iterationsperq > 0 ? s_maxnsec3iterationsperq : std::numeric_limits<decltype(d_validationContext.d_nsec3IterationsRemainingQuota)>::max();
579580
}
@@ -3569,7 +3570,10 @@ vector<ComboAddress> SyncRes::retrieveAddressesForNS(const std::string& prefix,
35693570
void SyncRes::checkMaxQperQ(const DNSName& qname) const
35703571
{
35713572
if (d_outqueries + d_throttledqueries > s_maxqperq) {
3572-
throw ImmediateServFailException("more than " + std::to_string(s_maxqperq) + " (max-qperq) queries sent or throttled while resolving " + qname.toLogString());
3573+
throw ImmediateServFailException("More than " + std::to_string(s_maxqperq) + " (outgoing.max_qperq) queries sent or throttled while resolving " + qname.toLogString());
3574+
}
3575+
if (d_bytesReceived > s_maxbytesperq) {
3576+
throw ImmediateServFailException("More than " + std::to_string(s_maxbytesperq) + " (outgoing.max_bytesperq) bytes received while resolving " + qname.toLogString());
35733577
}
35743578
}
35753579

@@ -4278,13 +4282,33 @@ static bool isRedirection(QType qtype)
42784282
return qtype == QType::CNAME || qtype == QType::DNAME;
42794283
}
42804284

4285+
// Walk the chain from qname, only adding names that can be reached
4286+
static std::unordered_set<DNSName> sanitizeCNAMEChain(const DNSName& qname, std::unordered_map<DNSName, DNSName>& cnameChain)
4287+
{
4288+
std::unordered_set<DNSName> allowed = {qname};
4289+
DNSName key{qname};
4290+
while (true) {
4291+
if (auto probe = cnameChain.find(key); probe != cnameChain.end()) {
4292+
allowed.emplace(probe->second);
4293+
key = probe->second;
4294+
// This will prevent looping in this function. CNAME loops themselves we handle higher up, see handleNewTarget()
4295+
cnameChain.erase(probe);
4296+
}
4297+
else {
4298+
break;
4299+
}
4300+
}
4301+
return allowed;
4302+
}
4303+
42814304
void SyncRes::sanitizeRecords(const std::string& prefix, LWResult& lwr, const DNSName& qname, const QType qtype, const DNSName& auth, bool wasForwarded, bool rdQuery)
42824305
{
42834306
const bool wasForwardRecurse = wasForwarded && rdQuery;
42844307
/* list of names for which we will allow A and AAAA records in the additional section
42854308
to remain */
42864309
std::unordered_set<DNSName> allowedAdditionals = {qname};
42874310
std::unordered_set<DNSName> allowedAnswerNames = {qname};
4311+
std::unordered_map<DNSName, DNSName> cnameChain;
42884312
bool cnameSeen = false;
42894313
bool haveAnswers = false;
42904314
bool acceptDelegation = false;
@@ -4359,7 +4383,7 @@ void SyncRes::sanitizeRecords(const std::string& prefix, LWResult& lwr, const DN
43594383
haveAnswers = true;
43604384
if (rec->d_type == QType::CNAME) {
43614385
if (auto cnametarget = getRR<CNAMERecordContent>(*rec); cnametarget != nullptr) {
4362-
allowedAnswerNames.insert(cnametarget->getTarget());
4386+
cnameChain.emplace(rec->d_name, cnametarget->getTarget());
43634387
}
43644388
cnameSeen = cnameSeen || qname == rec->d_name;
43654389
}
@@ -4423,6 +4447,10 @@ void SyncRes::sanitizeRecords(const std::string& prefix, LWResult& lwr, const DN
44234447
acceptDelegation = true;
44244448
}
44254449

4450+
if (cnameChain.size() > 0) {
4451+
auto allowed = sanitizeCNAMEChain(qname, cnameChain);
4452+
allowedAnswerNames.insert(allowed.begin(), allowed.end());
4453+
}
44264454
sanitizeRecordsPass2(prefix, lwr, qname, qtype, auth, allowedAnswerNames, allowedAdditionals, cnameSeen, acceptDelegation && !soaInAuth, skipvec, skipCount);
44274455
}
44284456

@@ -5564,6 +5592,7 @@ bool SyncRes::doResolveAtThisIP(const std::string& prefix, const DNSName& qname,
55645592
throw ImmediateServFailException("Query killed by policy");
55655593
}
55665594

5595+
d_bytesReceived += lwr.d_bytesReceived;
55675596
d_totUsec += lwr.d_usec;
55685597

55695598
if (resolveret == LWResult::Result::Spoofed) {
@@ -5883,6 +5912,13 @@ bool SyncRes::processAnswer(unsigned int depth, const string& prefix, LWResult&
58835912
nameservers.insert({nameserver, {{}, false}});
58845913
}
58855914
LOG("looping to them" << endl);
5915+
if (s_maxnsperresolve > 0 && nameservers.size() > s_maxnsperresolve) {
5916+
LOG(prefix << qname << "Reducing number of NS we are willing to consider to " << s_maxnsperresolve << endl);
5917+
NsSet selected;
5918+
std::sample(nameservers.cbegin(), nameservers.cend(), std::inserter(selected, selected.begin()), s_maxnsperresolve, pdns::dns_random_engine());
5919+
nameservers = std::move(selected);
5920+
}
5921+
58865922
*gotNewServers = true;
58875923
auth = std::move(newauth);
58885924

@@ -5936,11 +5972,17 @@ int SyncRes::doResolveAt(NsSet& nameservers, DNSName auth, bool flawedNSSet, con
59365972
if (rnameservers.size() > nsLimit) {
59375973
int newLimit = static_cast<int>(nsLimit - (rnameservers.size() - nsLimit));
59385974
nsLimit = std::max(5, newLimit);
5975+
LOG("Applying nsLimit " << nsLimit << endl);
59395976
}
59405977

5978+
// If multiple NS records resolve to the same IP, we don't want to ask again, so keep track
5979+
std::set<ComboAddress> visitedAddresses;
59415980
for (auto tns = rnameservers.cbegin();; ++tns) {
59425981
if (addressQueriesForNS >= nsLimit) {
5943-
throw ImmediateServFailException(std::to_string(nsLimit) + " (adjusted max-ns-address-qperq) or more queries with empty results for NS addresses sent resolving " + qname.toLogString());
5982+
throw ImmediateServFailException(std::to_string(nsLimit) + " (outgoing.max_ns_address_qperq) or more queries with empty results for NS addresses sent resolving " + qname.toLogString());
5983+
}
5984+
if (s_maxnsperresolve > 0 && visitedAddresses.size() > 2 * s_maxnsperresolve) {
5985+
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());
59445986
}
59455987
if (tns == rnameservers.cend()) {
59465988
LOG(prefix << qname << ": Failed to resolve via any of the " << (unsigned int)rnameservers.size() << " offered NS at level '" << auth << "'" << endl);
@@ -6038,6 +6080,11 @@ int SyncRes::doResolveAt(NsSet& nameservers, DNSName auth, bool flawedNSSet, con
60386080
}
60396081

60406082
for (remoteIP = remoteIPs.begin(); remoteIP != remoteIPs.end(); ++remoteIP) {
6083+
auto inserted = visitedAddresses.insert(*remoteIP).second;
6084+
if (!wasForwarded && !inserted) {
6085+
LOG(prefix << qname << ": Already visited " << remoteIP->toStringWithPort() << ", asking '" << qname << "|" << qtype << "'; skipping" << endl);
6086+
continue;
6087+
}
60416088
LOG(prefix << qname << ": Trying IP " << remoteIP->toStringWithPort() << ", asking '" << qname << "|" << qtype << "'" << endl);
60426089

60436090
if (throttledOrBlocked(prefix, *remoteIP, qname, qtype, pierceDontQuery)) {

0 commit comments

Comments
 (0)