return ret;
}
+ lwr->d_bytesReceived = len;
+
if (*chained) {
auto msec = lwr->d_usec / 1000;
if (msec > g_networkTimeoutMsec * 2 / 3) {
vector<DNSRecord> d_records;
uint32_t d_usec{0};
+ uint32_t d_bytesReceived{0};
int d_rcode{0};
bool d_validpacket{false};
bool d_aabit{false}, d_tcbit{false};
"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<double>(spentUsec) / 1000.0),
"throttled", Logging::Loggable(resolver.d_throttledqueries),
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");
'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.
''',
'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',
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;
}
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<decltype(d_validationContext.d_nsec3IterationsRemainingQuota)>::max();
}
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());
}
}
throw ImmediateServFailException("Query killed by policy");
}
+ d_bytesReceived += lwr.d_bytesReceived;
d_totUsec += lwr.d_usec;
if (resolveret == LWResult::Result::Spoofed || resolveret == LWResult::Result::BadCookie) {
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;
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:
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;
}
}
+BOOST_AUTO_TEST_CASE(test_edns_formerr_but_edns_enabled_limit_bytes)
+{
+ std::unique_ptr<SyncRes> 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<ComboAddress> usedServers;
+
+ sr->setAsyncCallback([&](const ComboAddress& address, const DNSName& /* domain */, int type, bool /* doTCP */, bool /* sendRDQuery */, int EDNS0Level, struct timeval* /* now */, std::optional<Netmask>& /* 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<DNSRecord> 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<SyncRes> sr;