#include "dnsname.hh"
#include "lock.hh"
-// this function can clean any cache that has a getTTD() method on its entries, a preRemoval() method and a 'sequence' index as its second index
+// this function can clean any cache that has an isStale() method on its entries, a preRemoval() method and a 'sequence' index as its second index
// the ritual is that the oldest entries are in *front* of the sequence collection, so on a hit, move an item to the end
// and optionally, on a miss, move it to the beginning
template <typename S, typename C, typename T>
size_t tried = 0, erased = 0;
for (auto iter = sidx.begin(); iter != sidx.end() && tried < lookAt; ++tried) {
- if (iter->getTTD() < now) {
+ if (iter->isStale(now)) {
iter = sidx.erase(iter);
erased++;
}
auto& sidx = boost::multi_index::get<S>(mc->d_map);
uint64_t erased = 0, lookedAt = 0;
for (auto i = sidx.begin(); i != sidx.end(); lookedAt++) {
- if (i->getTTD() < now) {
+ if (i->isStale(now)) {
container.preRemoval(*mc, *i);
i = sidx.erase(i);
erased++;
{
typedef vector<DNSSECKeeper::keymeta_t> keys_t;
- uint32_t getTTD() const
+ uint32_t isStale(time_t now) const
{
- return d_ttd;
+ return d_ttd < now;
}
DNSName d_domain;
struct METACacheEntry
{
- time_t getTTD() const
+ time_t isStale(time_t now) const
{
- return d_ttd;
+ return d_ttd < now;
}
DNSName d_domain;
const bool almostExpired = ttl <= deadline;
if (almostExpired) {
iter->d_submitted = true;
- pushAlmostExpiredTask(qname, qtype, iter->d_ttd);
+ pushAlmostExpiredTask(qname, qtype, iter->d_ttd, Netmask());
}
}
*responsePacket = iter->d_packet;
bool d_tcp; // whether this entry was created from a TCP query
inline bool operator<(const struct Entry& rhs) const;
- time_t getTTD() const
+ bool isStale(time_t now) const
{
- return d_ttd;
+ return d_ttd < now;
}
uint32_t getOrigTTL() const
#include "cachecleaner.hh"
#include "rec-taskqueue.hh"
+uint16_t MemRecursorCache::s_maxServedStaleExtensions;
+
MemRecursorCache::MemRecursorCache(size_t mapsCount) :
d_maps(mapsCount)
{
return ttd;
}
-MemRecursorCache::cache_t::const_iterator MemRecursorCache::getEntryUsingECSIndex(MapCombo::LockedContent& map, time_t now, const DNSName& qname, const QType qtype, bool requireAuth, const ComboAddress& who)
+static void pushRefreshTask(const DNSName& qname, QType qtype, time_t deadline, const Netmask& netmask)
+{
+ if (qtype == QType::ADDR) {
+ pushAlmostExpiredTask(qname, QType::A, deadline, netmask);
+ pushAlmostExpiredTask(qname, QType::AAAA, deadline, netmask);
+ }
+ else {
+ pushAlmostExpiredTask(qname, qtype, deadline, netmask);
+ }
+}
+
+void MemRecursorCache::updateStaleEntry(time_t now, MemRecursorCache::OrderedTagIterator_t& entry)
+{
+ // We need to take care a infrequently access stale item cannot be extended past
+ // s_maxServedStaleExtension * s_serveStaleExtensionPeriod
+ // We we look how old the entry is, and increase d_servedStale accordingly, taking care not to overflow
+ const time_t howlong = std::max(static_cast<time_t>(1), now - entry->d_ttd);
+ const uint32_t extension = std::max(1U, std::min(entry->d_orig_ttl, s_serveStaleExtensionPeriod));
+ entry->d_servedStale = std::min(entry->d_servedStale + 1 + howlong / extension, static_cast<time_t>(s_maxServedStaleExtensions));
+ entry->d_ttd = now + std::min(entry->d_orig_ttl, s_serveStaleExtensionPeriod);
+
+ pushRefreshTask(entry->d_qname, entry->d_qtype, entry->d_ttd, entry->d_netmask);
+}
+
+MemRecursorCache::cache_t::const_iterator MemRecursorCache::getEntryUsingECSIndex(MapCombo::LockedContent& map, time_t now, const DNSName& qname, const QType qtype, bool requireAuth, const ComboAddress& who, bool serveStale)
{
// MUTEX SHOULD BE ACQUIRED (as indicated by the reference to the content which is protected by a lock)
auto ecsIndexKey = std::tie(qname, qtype);
}
continue;
}
-
+ // If we are serving this record stale (or *should*) and the
+ // ttd has passed increase ttd to the future and remember that
+ // we did. Also push a refresh task.
+ if ((serveStale || entry->d_servedStale > 0) && entry->d_ttd <= now && entry->d_servedStale < s_maxServedStaleExtensions) {
+ updateStaleEntry(now, entry);
+ }
if (entry->d_ttd > now) {
if (!requireAuth || entry->d_auth) {
return entry;
else {
/* this netmask-specific entry has expired */
moveCacheItemToFront<SequencedTag>(map.d_map, entry);
- ecsIndex->removeNetmask(best);
- if (ecsIndex->isEmpty()) {
- map.d_ecsIndex.erase(ecsIndex);
- break;
+ // but is is also stale wrt serve-stale?
+ if (entry->isStale(now)) {
+ ecsIndex->removeNetmask(best);
+ if (ecsIndex->isEmpty()) {
+ map.d_ecsIndex.erase(ecsIndex);
+ }
}
+ break;
}
}
}
auto key = std::make_tuple(qname, qtype, boost::none, Netmask());
auto entry = map.d_map.find(key);
if (entry != map.d_map.end()) {
+ // If we are serving this record stale (or *should*) and the
+ // ttd has passed increase ttd to the future and remember that
+ // we did. Also push a refresh task.
+ if ((serveStale || entry->d_servedStale > 0) && entry->d_ttd <= now && entry->d_servedStale < s_maxServedStaleExtensions) {
+ updateStaleEntry(now, entry);
+ }
if (entry->d_ttd > now) {
if (!requireAuth || entry->d_auth) {
return entry;
time_t MemRecursorCache::fakeTTD(MemRecursorCache::OrderedTagIterator_t& entry, const DNSName& qname, QType qtype, time_t ret, time_t now, uint32_t origTTL, bool refresh)
{
time_t ttl = ret - now;
+ // If we are checking an entry being served stale in refresh mode,
+ // we always consider it stale so a real refresh attempt will be
+ // kicked by SyncRes
+ if (refresh && entry->d_servedStale > 0) {
+ return -1;
+ }
if (ttl > 0 && SyncRes::s_refresh_ttlperc > 0) {
const uint32_t deadline = origTTL * SyncRes::s_refresh_ttlperc / 100;
const bool almostExpired = static_cast<uint32_t>(ttl) <= deadline;
}
else {
if (!entry->d_submitted) {
- if (qtype == QType::ADDR) {
- pushAlmostExpiredTask(qname, QType::A, entry->d_ttd);
- pushAlmostExpiredTask(qname, QType::AAAA, entry->d_ttd);
- }
- else {
- pushAlmostExpiredTask(qname, qtype, entry->d_ttd);
- }
+ pushRefreshTask(qname, qtype, entry->d_ttd, entry->d_netmask);
entry->d_submitted = true;
}
}
{
bool requireAuth = flags & RequireAuth;
bool refresh = flags & Refresh;
+ bool serveStale = flags & ServeStale;
+
boost::optional<vState> cachedState{boost::none};
uint32_t origTTL;
if (qtype == QType::ADDR) {
time_t ret = -1;
- auto entryA = getEntryUsingECSIndex(*map, now, qname, QType::A, requireAuth, who);
+ auto entryA = getEntryUsingECSIndex(*map, now, qname, QType::A, requireAuth, who, serveStale);
if (entryA != map->d_map.end()) {
ret = handleHit(*map, entryA, qname, origTTL, res, signatures, authorityRecs, variable, cachedState, wasAuth, fromAuthZone, fromAuthIP);
}
- auto entryAAAA = getEntryUsingECSIndex(*map, now, qname, QType::AAAA, requireAuth, who);
+ auto entryAAAA = getEntryUsingECSIndex(*map, now, qname, QType::AAAA, requireAuth, who, serveStale);
if (entryAAAA != map->d_map.end()) {
time_t ttdAAAA = handleHit(*map, entryAAAA, qname, origTTL, res, signatures, authorityRecs, variable, cachedState, wasAuth, fromAuthZone, fromAuthIP);
if (ret > 0) {
return ret > 0 ? (ret - now) : ret;
}
else {
- auto entry = getEntryUsingECSIndex(*map, now, qname, qtype, requireAuth, who);
+ auto entry = getEntryUsingECSIndex(*map, now, qname, qtype, requireAuth, who, serveStale);
if (entry != map->d_map.end()) {
time_t ret = handleHit(*map, entry, qname, origTTL, res, signatures, authorityRecs, variable, cachedState, wasAuth, fromAuthZone, fromAuthIP);
if (state && cachedState) {
for (auto i = entries.first; i != entries.second; ++i) {
firstIndexIterator = map->d_map.project<OrderedTag>(i);
- if (i->d_ttd <= now) {
+ // When serving stale, we consider expired records
+ if (i->d_ttd <= now && !serveStale && i->d_servedStale == 0) {
moveCacheItemToFront<SequencedTag>(map->d_map, firstIndexIterator);
continue;
}
continue;
}
found = true;
+ // If we are serving this record stale (or *should*) and the
+ // ttd has passed increase ttd to the future and remember that
+ // we did. Also push a refresh task.
+ if ((serveStale || i->d_servedStale > 0) && i->d_ttd <= now && i->d_servedStale < s_maxServedStaleExtensions) {
+ updateStaleEntry(now, firstIndexIterator);
+ }
ttd = handleHit(*map, firstIndexIterator, qname, origTTL, res, signatures, authorityRecs, variable, cachedState, wasAuth, fromAuthZone, fromAuthIP);
if (qt != QType::ANY && qt != QType::ADDR) { // normally if we have a hit, we are done
for (auto i = entries.first; i != entries.second; ++i) {
firstIndexIterator = map->d_map.project<OrderedTag>(i);
- if (i->d_ttd <= now) {
+ // When serving stale, we consider expired records
+ if (i->d_ttd <= now && !serveStale && i->d_servedStale == 0) {
moveCacheItemToFront<SequencedTag>(map->d_map, firstIndexIterator);
continue;
}
if (!entryMatches(firstIndexIterator, qtype, requireAuth, who)) {
continue;
}
-
found = true;
+ // If we are serving this record stale (or *should*) and the
+ // ttd has passed increase ttd to the future and remember that
+ // we did. Also push a refresh task.
+ if ((serveStale || i->d_servedStale > 0) && i->d_ttd <= now && i->d_servedStale < s_maxServedStaleExtensions) {
+ updateStaleEntry(now, firstIndexIterator);
+ }
ttd = handleHit(*map, firstIndexIterator, qname, origTTL, res, signatures, authorityRecs, variable, cachedState, wasAuth, fromAuthZone, fromAuthIP);
if (qt != QType::ANY && qt != QType::ADDR) { // normally if we have a hit, we are done
moveCacheItemToBack<SequencedTag>(map->d_map, stored);
}
ce.d_submitted = false;
+ ce.d_servedStale = 0;
map->d_map.replace(stored, ce);
}
bool updated = false;
if (!map->d_ecsIndex.empty() && !routingTag) {
- auto entry = getEntryUsingECSIndex(*map, now, qname, qtype, requireAuth, who);
+ auto entry = getEntryUsingECSIndex(*map, now, qname, qtype, requireAuth, who, false); // XXX serveStale?
if (entry == map->d_map.end()) {
return false;
}
for (const auto& j : i.d_records) {
count++;
try {
- fprintf(fp.get(), "%s %" PRIu32 " %" PRId64 " IN %s %s ; (%s) auth=%i zone=%s from=%s %s %s\n", i.d_qname.toString().c_str(), i.d_orig_ttl, static_cast<int64_t>(i.d_ttd - now), i.d_qtype.toString().c_str(), j->getZoneRepresentation().c_str(), vStateToString(i.d_state).c_str(), i.d_auth, i.d_authZone.toLogString().c_str(), i.d_from.toString().c_str(), i.d_netmask.empty() ? "" : i.d_netmask.toString().c_str(), !i.d_rtag ? "" : i.d_rtag.get().c_str());
+ fprintf(fp.get(), "%s %" PRIu32 " %" PRId64 " IN %s %s ; (%s) auth=%i zone=%s from=%s nm=%s rtag=%s ss=%hd\n", i.d_qname.toString().c_str(), i.d_orig_ttl, static_cast<int64_t>(i.d_ttd - now), i.d_qtype.toString().c_str(), j->getZoneRepresentation().c_str(), vStateToString(i.d_state).c_str(), i.d_auth, i.d_authZone.toLogString().c_str(), i.d_from.toString().c_str(), i.d_netmask.empty() ? "" : i.d_netmask.toString().c_str(), !i.d_rtag ? "" : i.d_rtag.get().c_str(), i.d_servedStale);
}
catch (...) {
fprintf(fp.get(), "; error printing '%s'\n", i.d_qname.empty() ? "EMPTY" : i.d_qname.toString().c_str());
public:
MemRecursorCache(size_t mapsCount = 1024);
+ // The number of times a state cache entry is extended
+ static uint16_t s_maxServedStaleExtensions;
+ // The time a stale cache entry is extended
+ static constexpr uint32_t s_serveStaleExtensionPeriod = 30;
+
size_t size() const;
size_t bytes();
pair<uint64_t, uint64_t> stats();
struct CacheEntry
{
CacheEntry(const std::tuple<DNSName, QType, OptTag, Netmask>& key, bool auth) :
- d_qname(std::get<0>(key)), d_netmask(std::get<3>(key).getNormalized()), d_rtag(std::get<2>(key)), d_state(vState::Indeterminate), d_ttd(0), d_orig_ttl{0}, d_qtype(std::get<1>(key)), d_auth(auth), d_submitted(false)
+ d_qname(std::get<0>(key)), d_netmask(std::get<3>(key).getNormalized()), d_rtag(std::get<2>(key)), d_state(vState::Indeterminate), d_ttd(0), d_orig_ttl{0}, d_servedStale(0), d_qtype(std::get<1>(key)), d_auth(auth), d_submitted(false)
{
}
typedef vector<std::shared_ptr<DNSRecordContent>> records_t;
- time_t getTTD() const
+
+ bool isStale(time_t now) const
{
- return d_ttd;
+ // We like to keep things in cache when we (potentially) should serve stale
+ if (s_maxServedStaleExtensions > 0) {
+ return d_ttd + s_maxServedStaleExtensions * std::min(s_serveStaleExtensionPeriod, d_orig_ttl) < now;
+ }
+ else {
+ return d_ttd < now;
+ }
}
records_t d_records;
mutable vState d_state;
mutable time_t d_ttd;
uint32_t d_orig_ttl;
+ mutable uint16_t d_servedStale;
QType d_qtype;
bool d_auth;
mutable bool d_submitted; // whether this entry has been queued for refetch
bool entryMatches(OrderedTagIterator_t& entry, QType qt, bool requireAuth, const ComboAddress& who);
Entries getEntries(MapCombo::LockedContent& content, const DNSName& qname, const QType qt, const OptTag& rtag);
- cache_t::const_iterator getEntryUsingECSIndex(MapCombo::LockedContent& content, time_t now, const DNSName& qname, QType qtype, bool requireAuth, const ComboAddress& who);
+ cache_t::const_iterator getEntryUsingECSIndex(MapCombo::LockedContent& content, time_t now, const DNSName& qname, QType qtype, bool requireAuth, const ComboAddress& who, bool serveStale);
time_t handleHit(MapCombo::LockedContent& content, OrderedTagIterator_t& entry, const DNSName& qname, uint32_t& origTTL, vector<DNSRecord>* res, vector<std::shared_ptr<RRSIGRecordContent>>* signatures, std::vector<std::shared_ptr<DNSRecord>>* authorityRecs, bool* variable, boost::optional<vState>& state, bool* wasAuth, DNSName* authZone, ComboAddress* fromAuthIP);
+ void updateStaleEntry(time_t now, OrderedTagIterator_t& entry);
public:
void preRemoval(MapCombo::LockedContent& map, const CacheEntry& entry)
mutable time_t d_ttd; // Timestamp when this entry should die
mutable vState d_validationState{vState::Indeterminate};
QType d_qtype; // The denied type
- time_t getTTD() const
+ bool isStale(time_t now) const
{
- return d_ttd;
+ return d_ttd < now;
};
};
SyncRes::s_event_trace_enabled = ::arg().asNum("event-trace-enabled");
SyncRes::s_save_parent_ns_set = ::arg().mustDo("save-parent-ns-set");
SyncRes::s_max_busy_dot_probes = ::arg().asNum("max-busy-dot-probes");
+ MemRecursorCache::s_maxServedStaleExtensions = ::arg().asNum("serve-stale-extensions");
if (SyncRes::s_tcp_fast_open_connect) {
checkFastOpenSysctl(true, log);
::arg().set("structured-logging-backend", "Structured logging backend") = "default";
::arg().setSwitch("save-parent-ns-set", "Save parent NS set to be used if child NS set fails") = "yes";
::arg().set("max-busy-dot-probes", "Maximum number of concurrent DoT probes") = "0";
+ ::arg().set("serve-stale-extensions", "Number of times a record's ttl is extended by 30s to be served stale") = "0";
::arg().setCmd("help", "Provide a helpful message");
::arg().setCmd("version", "Print version string");
static void resolve(const struct timeval& now, bool logErrors, const pdns::ResolveTask& task) noexcept
{
- auto log = g_slog->withName("taskq")->withValues("name", Logging::Loggable(task.d_qname), "qtype", Logging::Loggable(QType(task.d_qtype).toString()));
+ auto log = g_slog->withName("taskq")->withValues("name", Logging::Loggable(task.d_qname), "qtype", Logging::Loggable(QType(task.d_qtype).toString()), "netmask", Logging::Loggable(task.d_netmask.empty() ? "" : task.d_netmask.toString()));
const string msg = "Exception while running a background ResolveTask";
SyncRes sr(now);
vector<DNSRecord> ret;
sr.setRefreshAlmostExpired(task.d_refreshMode);
+ sr.setQuerySource(task.d_netmask);
bool ex = true;
try {
- log->info(Logr::Debug, "resolving");
+ log->info(Logr::Debug, "resolving", "refresh", Logging::Loggable(task.d_refreshMode));
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()));
return true;
}
-void pushAlmostExpiredTask(const DNSName& qname, uint16_t qtype, time_t deadline)
+void pushAlmostExpiredTask(const DNSName& qname, uint16_t qtype, time_t deadline, const Netmask& netmask)
{
if (SyncRes::isUnsupported(qtype)) {
- auto log = g_slog->withName("taskq")->withValues("name", Logging::Loggable(qname), "qtype", Logging::Loggable(QType(qtype).toString()));
+ auto log = g_slog->withName("taskq")->withValues("name", Logging::Loggable(qname), "qtype", Logging::Loggable(QType(qtype).toString()), "netmask", Logging::Loggable(netmask.empty() ? "" : netmask.toString()));
log->error(Logr::Error, "Cannot push task", "qtype unsupported");
return;
}
- pdns::ResolveTask task{qname, qtype, deadline, true, resolve, {}, {}};
+ pdns::ResolveTask task{qname, qtype, deadline, true, resolve, {}, {}, netmask};
if (s_taskQueue.lock()->queue.push(std::move(task))) {
++s_almost_expired_tasks.pushed;
}
log->error(Logr::Error, "Cannot push task", "qtype unsupported");
return;
}
- pdns::ResolveTask task{qname, qtype, deadline, false, resolve, {}, {}};
+ pdns::ResolveTask task{qname, qtype, deadline, false, resolve, {}, {}, {}};
auto lock = s_taskQueue.lock();
bool inserted = lock->rateLimitSet.insert(now, task);
if (inserted) {
return false;
}
- pdns::ResolveTask task{qname, qtype, deadline, false, tryDoT, ip, nsname};
+ pdns::ResolveTask task{qname, qtype, deadline, false, tryDoT, ip, nsname, {}};
bool pushed = s_taskQueue.lock()->queue.push(std::move(task));
if (pushed) {
++s_almost_expired_tasks.pushed;
class DNSName;
union ComboAddress;
+class Netmask;
namespace pdns
{
}
void runTasks(size_t max, bool logErrors);
bool runTaskOnce(bool logErrors);
-void pushAlmostExpiredTask(const DNSName& qname, uint16_t qtype, time_t deadline);
+void pushAlmostExpiredTask(const DNSName& qname, uint16_t qtype, time_t deadline, const Netmask& netmask);
void pushResolveTask(const DNSName& qname, uint16_t qtype, time_t now, time_t deadline);
bool pushTryDoTTask(const DNSName& qname, uint16_t qtype, const ComboAddress& ip, time_t deadline, const DNSName& nsname);
void taskQueueClear();
}
#include <boost/multi_index_container.hpp>
-#include <boost/multi_index/hashed_index.hpp>
+#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/key_extractors.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/sequenced_index.hpp>
ComboAddress d_ip;
// NS name used by DoT probe task, not part of index and not used by operator<()
DNSName d_nsname;
+ Netmask d_netmask;
bool operator<(const ResolveTask& a) const
{
- return std::tie(d_qname, d_qtype, d_refreshMode, d_func, d_ip) < std::tie(a.d_qname, a.d_qtype, a.d_refreshMode, a.d_func, d_ip);
+ return std::tie(d_qname, d_qtype, d_refreshMode, d_func, d_ip, d_netmask) < std::tie(a.d_qname, a.d_qtype, a.d_refreshMode, a.d_func, a.d_ip, a.d_netmask);
}
bool run(bool logErrors);
typedef multi_index_container<
ResolveTask,
indexed_by<
- hashed_unique<tag<HashTag>,
+ ordered_unique<tag<HashTag>,
composite_key<ResolveTask,
member<ResolveTask, DNSName, &ResolveTask::d_qname>,
member<ResolveTask, uint16_t, &ResolveTask::d_qtype>,
member<ResolveTask, bool, &ResolveTask::d_refreshMode>,
member<ResolveTask, ResolveTask::TaskFunction, &ResolveTask::d_func>,
- member<ResolveTask, ComboAddress, &ResolveTask::d_ip>>>,
+ member<ResolveTask, ComboAddress, &ResolveTask::d_ip>,
+ member<ResolveTask, Netmask, &ResolveTask::d_netmask>>>,
sequenced<tag<SequencedTag>>>>
queue_t;
BOOST_AUTO_TEST_CASE(test_almostexpired_queue_no_dups)
{
taskQueueClear();
- pushAlmostExpiredTask(DNSName("foo"), QType::AAAA, 0);
- pushAlmostExpiredTask(DNSName("foo"), QType::AAAA, 0);
- pushAlmostExpiredTask(DNSName("foo"), QType::A, 0);
+ pushAlmostExpiredTask(DNSName("foo"), QType::AAAA, 0, Netmask());
+ pushAlmostExpiredTask(DNSName("foo"), QType::AAAA, 0, Netmask());
+ pushAlmostExpiredTask(DNSName("foo"), QType::A, 0, Netmask());
BOOST_CHECK_EQUAL(getTaskSize(), 2U);
taskQueuePop();
taskQueuePop();
BOOST_CHECK_EQUAL(getTaskSize(), 0U);
// AE queue is not rate limited
- pushAlmostExpiredTask(DNSName("foo"), QType::A, 0);
+ pushAlmostExpiredTask(DNSName("foo"), QType::A, 0, Netmask());
BOOST_CHECK_EQUAL(getTaskSize(), 1U);
}
}
int res=0;
- // This is a difficult way of expressing "this is a normal query", i.e. not getRootNS.
- if(!(d_updatingRootNS && qtype.getCode()==QType::NS && qname.isRoot())) {
- if(d_cacheonly) { // very limited OOB support
- LWResult lwr;
- LOG(prefix<<qname<<": Recursion not requested for '"<<qname<<"|"<<qtype<<"', peeking at auth/forward zones"<<endl);
- DNSName authname(qname);
- domainmap_t::const_iterator iter=getBestAuthZone(&authname);
- if(iter != t_sstorage.domainmap->end()) {
- if(iter->second.isAuth()) {
- ret.clear();
- d_wasOutOfBand = doOOBResolve(qname, qtype, ret, depth, res);
- if (fromCache)
- *fromCache = d_wasOutOfBand;
- return res;
- }
- else if (considerforwards) {
- const vector<ComboAddress>& servers = iter->second.d_servers;
- const ComboAddress remoteIP = servers.front();
- LOG(prefix<<qname<<": forwarding query to hardcoded nameserver '"<< remoteIP.toStringWithPort()<<"' for zone '"<<authname<<"'"<<endl);
-
- boost::optional<Netmask> nm;
- bool chained = false;
- // forwardes are "anonymous", so plug in an empty name; some day we might have a fancier config language...
- auto resolveRet = asyncresolveWrapper(remoteIP, d_doDNSSEC, qname, authname, qtype.getCode(), false, false, &d_now, nm, &lwr, &chained, DNSName());
-
- d_totUsec += lwr.d_usec;
- accountAuthLatency(lwr.d_usec, remoteIP.sin4.sin_family);
- if (fromCache)
- *fromCache = true;
-
- // filter out the good stuff from lwr.result()
- if (resolveRet == LWResult::Result::Success) {
- for(const auto& rec : lwr.d_records) {
- if(rec.d_place == DNSResourceRecord::ANSWER)
- ret.push_back(rec);
- }
- return 0;
+
+ const int iterations = !d_refresh && MemRecursorCache::s_maxServedStaleExtensions > 0 ? 2 : 1;
+ for (int loop = 0; loop < iterations; loop++) {
+
+ // First try a regular resolve
+ const bool serveStale = loop == 1;
+
+ // When we're not on the last iteration, a timeout is not fatal
+ d_exceptionOnTimeout = loop == iterations - 1;
+
+ // This is a difficult way of expressing "this is a normal query", i.e. not getRootNS.
+ if(!(d_updatingRootNS && qtype.getCode()==QType::NS && qname.isRoot())) {
+ if(d_cacheonly) { // very limited OOB support
+ LWResult lwr;
+ LOG(prefix<<qname<<": Recursion not requested for '"<<qname<<"|"<<qtype<<"', peeking at auth/forward zones"<<endl);
+ DNSName authname(qname);
+ domainmap_t::const_iterator iter=getBestAuthZone(&authname);
+ if(iter != t_sstorage.domainmap->end()) {
+ if(iter->second.isAuth()) {
+ ret.clear();
+ d_wasOutOfBand = doOOBResolve(qname, qtype, ret, depth, res);
+ if (fromCache)
+ *fromCache = d_wasOutOfBand;
+ return res;
}
- else {
- return RCode::ServFail;
+ else if (considerforwards) {
+ const vector<ComboAddress>& servers = iter->second.d_servers;
+ const ComboAddress remoteIP = servers.front();
+ LOG(prefix<<qname<<": forwarding query to hardcoded nameserver '"<< remoteIP.toStringWithPort()<<"' for zone '"<<authname<<"'"<<endl);
+
+ boost::optional<Netmask> nm;
+ bool chained = false;
+ // forwardes are "anonymous", so plug in an empty name; some day we might have a fancier config language...
+ auto resolveRet = asyncresolveWrapper(remoteIP, d_doDNSSEC, qname, authname, qtype.getCode(), false, false, &d_now, nm, &lwr, &chained, DNSName());
+
+ d_totUsec += lwr.d_usec;
+ accountAuthLatency(lwr.d_usec, remoteIP.sin4.sin_family);
+ if (fromCache)
+ *fromCache = true;
+
+ // filter out the good stuff from lwr.result()
+ if (resolveRet == LWResult::Result::Success) {
+ for(const auto& rec : lwr.d_records) {
+ if(rec.d_place == DNSResourceRecord::ANSWER)
+ ret.push_back(rec);
+ }
+ return 0;
+ }
+ else {
+ return RCode::ServFail;
+ }
}
}
}
- }
-
- DNSName authname(qname);
- bool wasForwardedOrAuthZone = false;
- bool wasAuthZone = false;
- bool wasForwardRecurse = false;
- domainmap_t::const_iterator iter = getBestAuthZone(&authname);
- if(iter != t_sstorage.domainmap->end()) {
- const auto& domain = iter->second;
- wasForwardedOrAuthZone = true;
-
- if (domain.isAuth()) {
- wasAuthZone = true;
- } else if (domain.shouldRecurse()) {
- wasForwardRecurse = true;
- }
- }
- /* When we are looking for a DS, we want to the non-CNAME cache check first
- because we can actually have a DS (from the parent zone) AND a CNAME (from
- the child zone), and what we really want is the DS */
- if (qtype != QType::DS && doCNAMECacheCheck(qname, qtype, ret, depth, res, state, wasAuthZone, wasForwardRecurse)) { // will reroute us if needed
- d_wasOutOfBand = wasAuthZone;
- // Here we have an issue. If we were prevented from going out to the network (cache-only was set, possibly because we
- // are in QM Step0) we might have a CNAME but not the corresponding target.
- // It means that we will sometimes go to the next steps when we are in fact done, but that's fine since
- // we will get the records from the cache, resulting in a small overhead.
- // This might be a real problem if we had a RPZ hit, though, because we do not want the processing to continue, since
- // RPZ rules will not be evaluated anymore (we already matched).
- const bool stoppedByPolicyHit = d_appliedPolicy.wasHit();
+ DNSName authname(qname);
+ bool wasForwardedOrAuthZone = false;
+ bool wasAuthZone = false;
+ bool wasForwardRecurse = false;
+ domainmap_t::const_iterator iter = getBestAuthZone(&authname);
+ if(iter != t_sstorage.domainmap->end()) {
+ const auto& domain = iter->second;
+ wasForwardedOrAuthZone = true;
- if (fromCache && (!d_cacheonly || stoppedByPolicyHit)) {
- *fromCache = true;
+ if (domain.isAuth()) {
+ wasAuthZone = true;
+ } else if (domain.shouldRecurse()) {
+ wasForwardRecurse = true;
+ }
}
- /* Apply Post filtering policies */
- if (d_wantsRPZ && !stoppedByPolicyHit) {
- auto luaLocal = g_luaconfs.getLocal();
- if (luaLocal->dfe.getPostPolicy(ret, d_discardedPolicies, d_appliedPolicy)) {
- mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
- bool done = false;
- handlePolicyHit(prefix, qname, qtype, ret, done, res, depth);
- if (done && fromCache) {
- *fromCache = true;
+ /* When we are looking for a DS, we want to the non-CNAME cache check first
+ because we can actually have a DS (from the parent zone) AND a CNAME (from
+ the child zone), and what we really want is the DS */
+ if (qtype != QType::DS && doCNAMECacheCheck(qname, qtype, ret, depth, res, state, wasAuthZone, wasForwardRecurse, serveStale)) { // will reroute us if needed
+ d_wasOutOfBand = wasAuthZone;
+ // Here we have an issue. If we were prevented from going out to the network (cache-only was set, possibly because we
+ // are in QM Step0) we might have a CNAME but not the corresponding target.
+ // It means that we will sometimes go to the next steps when we are in fact done, but that's fine since
+ // we will get the records from the cache, resulting in a small overhead.
+ // This might be a real problem if we had a RPZ hit, though, because we do not want the processing to continue, since
+ // RPZ rules will not be evaluated anymore (we already matched).
+ const bool stoppedByPolicyHit = d_appliedPolicy.wasHit();
+
+ if (fromCache && (!d_cacheonly || stoppedByPolicyHit)) {
+ *fromCache = true;
+ }
+ /* Apply Post filtering policies */
+
+ if (d_wantsRPZ && !stoppedByPolicyHit) {
+ auto luaLocal = g_luaconfs.getLocal();
+ if (luaLocal->dfe.getPostPolicy(ret, d_discardedPolicies, d_appliedPolicy)) {
+ mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
+ bool done = false;
+ handlePolicyHit(prefix, qname, qtype, ret, done, res, depth);
+ if (done && fromCache) {
+ *fromCache = true;
+ }
}
}
+ return res;
}
- return res;
- }
-
- if (doCacheCheck(qname, authname, wasForwardedOrAuthZone, wasAuthZone, wasForwardRecurse, qtype, ret, depth, res, state)) {
- // we done
- d_wasOutOfBand = wasAuthZone;
- if (fromCache) {
- *fromCache = true;
- }
-
- if (d_wantsRPZ && !d_appliedPolicy.wasHit()) {
- auto luaLocal = g_luaconfs.getLocal();
- if (luaLocal->dfe.getPostPolicy(ret, d_discardedPolicies, d_appliedPolicy)) {
- mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
- bool done = false;
- handlePolicyHit(prefix, qname, qtype, ret, done, res, depth);
+ if (doCacheCheck(qname, authname, wasForwardedOrAuthZone, wasAuthZone, wasForwardRecurse, qtype, ret, depth, res, state, serveStale)) {
+ // we done
+ d_wasOutOfBand = wasAuthZone;
+ if (fromCache) {
+ *fromCache = true;
}
- }
- return res;
- }
-
- /* if we have not found a cached DS (or denial of), now is the time to look for a CNAME */
- if (qtype == QType::DS && doCNAMECacheCheck(qname, qtype, ret, depth, res, state, wasAuthZone, wasForwardRecurse)) { // will reroute us if needed
- d_wasOutOfBand = wasAuthZone;
- // Here we have an issue. If we were prevented from going out to the network (cache-only was set, possibly because we
- // are in QM Step0) we might have a CNAME but not the corresponding target.
- // It means that we will sometimes go to the next steps when we are in fact done, but that's fine since
- // we will get the records from the cache, resulting in a small overhead.
- // This might be a real problem if we had a RPZ hit, though, because we do not want the processing to continue, since
- // RPZ rules will not be evaluated anymore (we already matched).
- const bool stoppedByPolicyHit = d_appliedPolicy.wasHit();
+ if (d_wantsRPZ && !d_appliedPolicy.wasHit()) {
+ auto luaLocal = g_luaconfs.getLocal();
+ if (luaLocal->dfe.getPostPolicy(ret, d_discardedPolicies, d_appliedPolicy)) {
+ mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
+ bool done = false;
+ handlePolicyHit(prefix, qname, qtype, ret, done, res, depth);
+ }
+ }
- if (fromCache && (!d_cacheonly || stoppedByPolicyHit)) {
- *fromCache = true;
+ return res;
}
- /* Apply Post filtering policies */
- if (d_wantsRPZ && !stoppedByPolicyHit) {
- auto luaLocal = g_luaconfs.getLocal();
- if (luaLocal->dfe.getPostPolicy(ret, d_discardedPolicies, d_appliedPolicy)) {
- mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
- bool done = false;
- handlePolicyHit(prefix, qname, qtype, ret, done, res, depth);
- if (done && fromCache) {
- *fromCache = true;
+ /* if we have not found a cached DS (or denial of), now is the time to look for a CNAME */
+ if (qtype == QType::DS && doCNAMECacheCheck(qname, qtype, ret, depth, res, state, wasAuthZone, wasForwardRecurse, serveStale)) { // will reroute us if needed
+ d_wasOutOfBand = wasAuthZone;
+ // Here we have an issue. If we were prevented from going out to the network (cache-only was set, possibly because we
+ // are in QM Step0) we might have a CNAME but not the corresponding target.
+ // It means that we will sometimes go to the next steps when we are in fact done, but that's fine since
+ // we will get the records from the cache, resulting in a small overhead.
+ // This might be a real problem if we had a RPZ hit, though, because we do not want the processing to continue, since
+ // RPZ rules will not be evaluated anymore (we already matched).
+ const bool stoppedByPolicyHit = d_appliedPolicy.wasHit();
+
+ if (fromCache && (!d_cacheonly || stoppedByPolicyHit)) {
+ *fromCache = true;
+ }
+ /* Apply Post filtering policies */
+
+ if (d_wantsRPZ && !stoppedByPolicyHit) {
+ auto luaLocal = g_luaconfs.getLocal();
+ if (luaLocal->dfe.getPostPolicy(ret, d_discardedPolicies, d_appliedPolicy)) {
+ mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
+ bool done = false;
+ handlePolicyHit(prefix, qname, qtype, ret, done, res, depth);
+ if (done && fromCache) {
+ *fromCache = true;
+ }
}
}
- }
- return res;
+ return res;
+ }
}
- }
-
- if (d_cacheonly) {
- return 0;
- }
-
- LOG(prefix<<qname<<": No cache hit for '"<<qname<<"|"<<qtype<<"', trying to find an appropriate NS record"<<endl);
- DNSName subdomain(qname);
- if (qtype == QType::DS) subdomain.chopOff();
+ if (d_cacheonly) {
+ return 0;
+ }
- NsSet nsset;
- bool flawedNSSet=false;
+ LOG(prefix<<qname<<": No cache hit for '"<<qname<<"|"<<qtype<<"', trying to find an appropriate NS record"<<endl);
- // the two retries allow getBestNSNamesFromCache&co to reprime the root
- // hints, in case they ever go missing
- for(int tries=0;tries<2 && nsset.empty();++tries) {
- subdomain=getBestNSNamesFromCache(subdomain, qtype, nsset, &flawedNSSet, depth, beenthere); // pass beenthere to both occasions
- }
+ DNSName subdomain(qname);
+ if (qtype == QType::DS) subdomain.chopOff();
- res = doResolveAt(nsset, subdomain, flawedNSSet, qname, qtype, ret, depth, beenthere, state, stopAtDelegation, nullptr);
+ NsSet nsset;
+ bool flawedNSSet=false;
- if (res == -1 && s_save_parent_ns_set) {
- // It did not work out, lets check if we have a saved parent NS set
- map<DNSName, vector<ComboAddress>> fallBack;
- {
- auto lock = s_savedParentNSSet.lock();
- auto domainData= lock->find(subdomain);
- if (domainData != lock->end() && domainData->d_nsAddresses.size() > 0) {
- nsset.clear();
- // Build the nsset arg and fallBack data for the fallback doResolveAt() attempt
- // Take a copy to be able to release the lock, NsSet is actually a map, go figure
- for (const auto& ns : domainData->d_nsAddresses) {
- nsset.emplace(ns.first, pair(std::vector<ComboAddress>(), false));
- fallBack.emplace(ns.first, ns.second);
+ // the two retries allow getBestNSNamesFromCache&co to reprime the root
+ // hints, in case they ever go missing
+ for(int tries=0;tries<2 && nsset.empty();++tries) {
+ subdomain=getBestNSNamesFromCache(subdomain, qtype, nsset, &flawedNSSet, depth, beenthere); // pass beenthere to both occasions
+ }
+
+ res = doResolveAt(nsset, subdomain, flawedNSSet, qname, qtype, ret, depth, beenthere, state, stopAtDelegation, nullptr);
+
+ if (res == -1 && s_save_parent_ns_set) {
+ // It did not work out, lets check if we have a saved parent NS set
+ map<DNSName, vector<ComboAddress>> fallBack;
+ {
+ auto lock = s_savedParentNSSet.lock();
+ auto domainData= lock->find(subdomain);
+ if (domainData != lock->end() && domainData->d_nsAddresses.size() > 0) {
+ nsset.clear();
+ // Build the nsset arg and fallBack data for the fallback doResolveAt() attempt
+ // Take a copy to be able to release the lock, NsSet is actually a map, go figure
+ for (const auto& ns : domainData->d_nsAddresses) {
+ nsset.emplace(ns.first, pair(std::vector<ComboAddress>(), false));
+ fallBack.emplace(ns.first, ns.second);
+ }
+ }
+ }
+ if (fallBack.size() > 0) {
+ LOG(prefix<<qname<<": Failure, but we have a saved parent NS set, trying that one"<< endl)
+ res = doResolveAt(nsset, subdomain, flawedNSSet, qname, qtype, ret, depth, beenthere, state, stopAtDelegation, &fallBack);
+ if (res == 0) {
+ // It did work out
+ s_savedParentNSSet.lock()->inc(subdomain);
}
}
}
- if (fallBack.size() > 0) {
- LOG(prefix<<qname<<": Failure, but we have a saved parent NS set, trying that one"<< endl)
- res = doResolveAt(nsset, subdomain, flawedNSSet, qname, qtype, ret, depth, beenthere, state, stopAtDelegation, &fallBack);
- if (res == 0) {
- // It did work out
- s_savedParentNSSet.lock()->inc(subdomain);
+ /* Apply Post filtering policies */
+ if (d_wantsRPZ && !d_appliedPolicy.wasHit()) {
+ auto luaLocal = g_luaconfs.getLocal();
+ if (luaLocal->dfe.getPostPolicy(ret, d_discardedPolicies, d_appliedPolicy)) {
+ mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
+ bool done = false;
+ handlePolicyHit(prefix, qname, qtype, ret, done, res, depth);
}
}
- }
- /* Apply Post filtering policies */
- if (d_wantsRPZ && !d_appliedPolicy.wasHit()) {
- auto luaLocal = g_luaconfs.getLocal();
- if (luaLocal->dfe.getPostPolicy(ret, d_discardedPolicies, d_appliedPolicy)) {
- mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
- bool done = false;
- handlePolicyHit(prefix, qname, qtype, ret, done, res, depth);
+
+ if (!res) {
+ return 0;
}
- }
- if (!res) {
- return 0;
+ LOG(prefix<<qname<<": failed (res="<<res<<")"<<endl);
}
-
- LOG(prefix<<qname<<": failed (res="<<res<<")"<<endl);
-
return res<0 ? RCode::ServFail : res;
}
return false;
}
-bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType qtype, vector<DNSRecord>& ret, unsigned int depth, int &res, vState& state, bool wasAuthZone, bool wasForwardRecurse)
+bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType qtype, vector<DNSRecord>& ret, unsigned int depth, int &res, vState& state, bool wasAuthZone, bool wasForwardRecurse, bool serveStale)
{
string prefix;
if(doLog()) {
if (d_refresh) {
flags |= MemRecursorCache::Refresh;
}
+ if (serveStale) {
+ flags |= MemRecursorCache::ServeStale;
+ }
if (g_recCache->get(d_now.tv_sec, qname, QType::CNAME, flags, &cset, d_cacheRemote, d_routingTag, d_doDNSSEC ? &signatures : nullptr, d_doDNSSEC ? &authorityRecs : nullptr, &d_wasVariable, &state, &wasAuth, &authZone, &d_fromAuthIP) > 0) {
foundName = qname;
foundQT = QType::CNAME;
}
}
-bool SyncRes::doCacheCheck(const DNSName &qname, const DNSName& authname, bool wasForwardedOrAuthZone, bool wasAuthZone, bool wasForwardRecurse, QType qtype, vector<DNSRecord>&ret, unsigned int depth, int &res, vState& state)
+bool SyncRes::doCacheCheck(const DNSName &qname, const DNSName& authname, bool wasForwardedOrAuthZone, bool wasAuthZone, bool wasForwardRecurse, QType qtype, vector<DNSRecord>&ret, unsigned int depth, int &res, vState& state, bool serveStale)
{
bool giveNegative=false;
if (d_refresh) {
flags |= MemRecursorCache::Refresh;
}
-
+ if (serveStale) {
+ flags |= MemRecursorCache::ServeStale;
+ }
if(g_recCache->get(d_now.tv_sec, sqname, sqt, flags, &cset, d_cacheRemote, d_routingTag, d_doDNSSEC ? &signatures : nullptr, d_doDNSSEC ? &authorityRecs : nullptr, &d_wasVariable, &cachedState, &wasCachedAuth, nullptr, &d_fromAuthIP) > 0) {
LOG(prefix<<sqname<<": Found cache hit for "<<sqt.toString()<<": ");
checkMaxQperQ(qname);
if(s_maxtotusec && d_totUsec > s_maxtotusec) {
- throw ImmediateServFailException("Too much time waiting for "+qname.toLogString()+"|"+qtype.toString()+", timeouts: "+std::to_string(d_timeouts) +", throttles: "+std::to_string(d_throttledqueries) + ", queries: "+std::to_string(d_outqueries)+", "+std::to_string(d_totUsec/1000)+"msec");
+ if (d_exceptionOnTimeout) {
+ throw ImmediateServFailException("Too much time waiting for "+qname.toLogString()+"|"+qtype.toString()+", timeouts: "+std::to_string(d_timeouts) +", throttles: "+std::to_string(d_throttledqueries) + ", queries: "+std::to_string(d_outqueries)+", "+std::to_string(d_totUsec/1000)+"msec");
+ } else {
+ return false;
+ }
}
if(doTCP) {
return -1;
}
+void SyncRes::setQuerySource(const Netmask& netmask)
+{
+ if (!netmask.empty()) {
+ d_outgoingECSNetwork = netmask;
+ }
+ else {
+ d_outgoingECSNetwork = boost::none;
+ }
+}
+
void SyncRes::setQuerySource(const ComboAddress& requestor, boost::optional<const EDNSSubnetOpts&> incomingECS)
{
d_requestor = requestor;
}
void setQuerySource(const ComboAddress& requestor, boost::optional<const EDNSSubnetOpts&> incomingECS);
+ void setQuerySource(const Netmask& netmask);
void setInitialRequestId(boost::optional<const boost::uuids::uuid&> initialRequestId)
{
bool isRecursiveForwardOrAuth(const DNSName &qname) const;
bool isForwardOrAuth(const DNSName &qname) const;
domainmap_t::const_iterator getBestAuthZone(DNSName* qname) const;
- bool doCNAMECacheCheck(const DNSName &qname, QType qtype, vector<DNSRecord>&ret, unsigned int depth, int &res, vState& state, bool wasAuthZone, bool wasForwardRecurse);
- bool doCacheCheck(const DNSName &qname, const DNSName& authname, bool wasForwardedOrAuthZone, bool wasAuthZone, bool wasForwardRecurse, QType qtype, vector<DNSRecord>&ret, unsigned int depth, int &res, vState& state);
+ bool doCNAMECacheCheck(const DNSName &qname, QType qtype, vector<DNSRecord>&ret, unsigned int depth, int &res, vState& state, bool wasAuthZone, bool wasForwardRecurse, bool serveStale);
+ bool doCacheCheck(const DNSName &qname, const DNSName& authname, bool wasForwardedOrAuthZone, bool wasAuthZone, bool wasForwardRecurse, QType qtype, vector<DNSRecord>&ret, unsigned int depth, int &res, vState& state, bool serveStale);
void getBestNSFromCache(const DNSName &qname, QType qtype, vector<DNSRecord>&bestns, bool* flawedNSSet, unsigned int depth, set<GetBestNSAnswer>& beenthere, const boost::optional<DNSName>& cutOffDomain = boost::none);
DNSName getBestNSNamesFromCache(const DNSName &qname, QType qtype, NsSet& nsset, bool* flawedNSSet, unsigned int depth, set<GetBestNSAnswer>&beenthere);
bool d_queryReceivedOverTCP{false};
bool d_followCNAME{true};
bool d_refresh{false};
-
+ bool d_exceptionOnTimeout{true};
+
LogMode d_lm;
};