#include "stat_t.hh"
#include "syncres.hh"
-struct Queues
+// For rate lmiting, we maintain a set of tasks recently submitted.
+class TimedSet
+{
+public:
+ TimedSet(time_t t) :
+ d_expiry_seconds(t)
+ {
+ }
+ bool insert(time_t now, const pdns::ResolveTask& task)
+ {
+ time_t ttd = now + d_expiry_seconds;
+ bool inserted = d_set.emplace(task, ttd).second;
+ if (!inserted) {
+ // Instead of a periodic clean, we always do it on a hit
+ // the operation should be cheap as we just walk the ordered time_t index
+ // There is a slim chance if we never hit a rate limiting case we'll never clean... oh well
+ auto& ind = d_set.template get<time_t>();
+ auto it = ind.begin();
+ bool erased = false;
+ while (it != ind.end()) {
+ if (it->d_ttd < now) {
+ erased = true;
+ it = ind.erase(it);
+ }
+ else {
+ break;
+ }
+ }
+ // Try again if the loop deleted at least one entry
+ if (erased) {
+ inserted = d_set.emplace(task, ttd).second;
+ }
+ }
+ return inserted;
+ }
+
+private:
+ struct Entry
+ {
+ Entry(const pdns::ResolveTask& task, time_t ttd) :
+ d_task(task), d_ttd(ttd) {}
+ pdns::ResolveTask d_task;
+ time_t d_ttd;
+ };
+
+ typedef multi_index_container<Entry,
+ indexed_by<
+ ordered_unique<tag<pdns::ResolveTask>, member<Entry, pdns::ResolveTask, &Entry::d_task>>,
+ ordered_non_unique<tag<time_t>, member<Entry, time_t, &Entry::d_ttd>>>>
+ timed_set_t;
+ timed_set_t d_set;
+ time_t d_expiry_seconds;
+};
+
+struct Queue
{
pdns::TaskQueue queue;
- std::set<pdns::ResolveTask> running;
+ TimedSet rateLimitSet{60};
};
-static LockGuarded<Queues> s_taskQueue;
+static LockGuarded<Queue> s_taskQueue;
struct taskstats
{
log->info(Logr::Debug, "resolving");
int res = sr.beginResolve(task.d_qname, QType(task.d_qtype), QClass::IN, ret);
ex = false;
- log->info(Logr::Debug, "done", "rcode", Logging::Loggable(res), "records", Logging::Loggable(ret.size()));
+ log->info(Logr::Debug, "done", "rcode", Logging::Loggable(res), "records", Logging::Loggable(ret.size()));
}
catch (const std::exception& e) {
log->error(Logr::Error, msg, e.what());
return;
}
task = lock->queue.pop();
- lock->running.insert(task);
}
bool expired = task.run(logErrors);
- s_taskQueue.lock()->running.erase(task);
if (expired) {
s_taskQueue.lock()->queue.incExpired();
}
{
pdns::ResolveTask task{qname, qtype, deadline, true, resolve};
auto lock = s_taskQueue.lock();
- bool running = lock->running.count(task) > 0;
+ bool running = !lock->rateLimitSet.insert(time(nullptr), task);
if (!running) {
++s_almost_expired_tasks.pushed;
lock->queue.push(std::move(task));
{
pdns::ResolveTask task{qname, qtype, deadline, false, resolve};
auto lock = s_taskQueue.lock();
- bool running = lock->running.count(task) > 0;
+ bool running = !lock->rateLimitSet.insert(time(nullptr), task);
if (!running) {
++s_resolve_tasks.pushed;
lock->queue.push(std::move(task));
*/
#pragma once
-#include "dnsname.hh"
+#include <cstdint>
+#include <time.h>
+
+class DNSName;
void runTaskOnce(bool logErrors);
void pushAlmostExpiredTask(const DNSName& qname, uint16_t qtype, time_t deadline);
bool ResolveTask::run(bool logErrors)
{
- if (!d_func) {
+ if (d_func == nullptr) {
auto log = g_slog->withName("taskq")->withValues("name", Logging::Loggable(d_qname), "qtype", Logging::Loggable(QType(d_qtype).toString()));
log->error(Logr::Debug, "null task");
return false;
uint16_t d_qtype;
time_t d_deadline;
bool d_refreshMode; // Whether to run this task in regular mode (false) or in the mode that refreshes almost expired tasks
- std::function<void(const struct timeval& now, bool logErrors, const ResolveTask& task)> d_func;
+ // Use a function ponter as comparing std::functions is a nuisance
+ void (*d_func)(const struct timeval& now, bool logErrors, const ResolveTask& task);
bool operator<(const ResolveTask& a) const
{
- return std::tie(d_qname, d_qtype, d_refreshMode) < std::tie(d_qname, d_qtype, d_refreshMode);
+ return std::tie(d_qname, d_qtype, d_refreshMode, d_func) < std::tie(a.d_qname, a.d_qtype, a.d_refreshMode, a.d_func);
}
bool run(bool logErrors);
};
BOOST_REQUIRE_EQUAL(ret.size(), 0U);
BOOST_CHECK_EQUAL(SyncRes::getNonResolvingNSSize(), 2U);
- // Originally empty NoData results where not cached, now they are
- BOOST_CHECK_EQUAL(g_negCache->size(), 4U);
- g_negCache->clear();
-
// Again, should not change anything
res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
BOOST_CHECK_EQUAL(res, RCode::ServFail);
BOOST_CHECK_EQUAL(SyncRes::getNonResolvingNSSize(), 2U);
- // Originally empty NoData results where not cached, now they are
- BOOST_CHECK_EQUAL(g_negCache->size(), 4U);
- g_negCache->clear();
-
// Again, but now things should start working because of the queryCounter getting high enough
// and one entry remains in the non-resolving cache
res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
BOOST_CHECK_EQUAL(res, RCode::NoError);
BOOST_CHECK_EQUAL(sr->getValidationState(), vState::BogusMissingNegativeIndication);
BOOST_REQUIRE_EQUAL(ret.size(), 0U);
- /* we don store empty results */
- BOOST_CHECK_EQUAL(queriesCount, 3U);
+ /* we don't store empty results */
+ BOOST_CHECK_EQUAL(queriesCount, 4U);
}
BOOST_AUTO_TEST_CASE(test_auth_zone_oob)
BOOST_CHECK_EQUAL(res, RCode::NoError);
BOOST_CHECK_EQUAL(sr->getValidationState(), vState::BogusMissingNegativeIndication);
BOOST_REQUIRE_EQUAL(ret.size(), 0U);
- /* we do store empty results */
- BOOST_CHECK_EQUAL(queriesCount, 3U);
+ /* we don't store empty results */
+ BOOST_CHECK_EQUAL(queriesCount, 4U);
}
BOOST_AUTO_TEST_CASE(test_dnssec_bogus_nxdomain)
uint32_t dnameTTL = 0;
bool referralOnDS = false;
- if (lwr.d_rcode == 0 && qtype.getCode() != 0 && lwr.d_records.size() == 0) {
- // NODATA and no SOA, put that into the negcache for a while
- NegCache::NegCacheEntry ne;
- ne.d_auth = auth;
- ne.d_name = qname;
- ne.d_qtype = qtype;
- ne.d_ttd = d_now.tv_sec + std::max(s_minimumTTL, 60U);
- ne.d_validationState = vState::BogusMissingNegativeIndication;
- LOG(prefix<<qname<<": got an completely empty response for"<<qname<<"/"<<qtype<<", putting into negcache"<<endl);
- g_negCache->add(ne);
- }
-
for (auto& rec : lwr.d_records) {
if (rec.d_type != QType::OPT && rec.d_class != QClass::IN) {
continue;