return new uint64_t(SyncRes::doDumpNonResolvingNS(fd));
}
+static uint64_t* pleaseDumpDoTProbeMap(int fd)
+{
+ return new uint64_t(SyncRes::doDumpDoTProbeMap(fd));
+}
+
// Generic dump to file command
static RecursorControlChannel::Answer doDumpToFile(int s, uint64_t* (*function)(int s), const string& name, bool threads = true)
{
"clear-nta [DOMAIN]... Clear the Negative Trust Anchor for DOMAINs, if no DOMAIN is specified, remove all\n"
"clear-ta [DOMAIN]... Clear the Trust Anchor for DOMAINs\n"
"dump-cache <filename> dump cache contents to the named file\n"
+ "dump-dot-probe-map <filename> dump the contents of the DoT probe map to the named file\n"
"dump-edns [status] <filename> dump EDNS status to the named file\n"
"dump-failedservers <filename> dump the failed servers to the named file\n"
"dump-non-resolving <filename> dump non-resolving nameservers addresses to the named file\n"
if (cmd == "dump-cache") {
return doDumpCache(s);
}
+ if (cmd == "dump-dot-probe-map") {
+ return doDumpToFile(s, pleaseDumpDoTProbeMap, cmd, false);
+ }
if (cmd == "dump-ednsstatus" || cmd == "dump-edns") {
return doDumpToFile(s, pleaseDumpEDNSMap, cmd);
}
"dump-rpz",
"dump-throttlemap",
"dump-non-resolving",
- "dump-saved-parent-ns-sets"};
+ "dump-saved-parent-ns-sets",
+ "dump-dot-probe-map"};
try {
initArguments(argc, argv);
string sockname = "pdns_recursor";
// Likley a few handler tasks could be moved to the taskThread
if (info.isTaskThread()) {
// TaskQueue is run always
- runTaskOnce(g_logCommonErrors);
+ runTasks(g_logCommonErrors, 10);
static PeriodicTask ztcTask{"ZTC", 60};
static map<DNSName, RecZoneToCache::State> ztcStates;
}
}
+static void tryDoT(const struct timeval& now, bool logErrors, const pdns::ResolveTask& task) noexcept
+{
+ auto log = g_slog->withName("taskq")->withValues("method", Logging::Loggable("tryDoT"), "name", Logging::Loggable(task.d_qname), "qtype", Logging::Loggable(QType(task.d_qtype).toString()), "ip", Logging::Loggable(task.d_ip));
+ const string msg = "Exception while running a background tryDoT task";
+ SyncRes sr(now);
+ vector<DNSRecord> ret;
+ sr.setRefreshAlmostExpired(false);
+ bool ex = true;
+ try {
+ log->info(Logr::Warning, "trying DoT");
+ bool ok = sr.tryDoT(task.d_qname, QType(task.d_qtype), DNSName("auth"), DNSName("ns"), task.d_ip, now.tv_sec);
+ ex = false;
+ log->info(Logr::Warning, "done", "ok", Logging::Loggable(ok));
+ }
+ catch (const std::exception& e) {
+ log->error(Logr::Error, msg, e.what());
+ }
+ catch (const PDNSException& e) {
+ log->error(Logr::Error, msg, e.reason);
+ }
+ catch (const ImmediateServFailException& e) {
+ if (logErrors) {
+ log->error(Logr::Error, msg, e.reason);
+ }
+ }
+ catch (const PolicyHitException& e) {
+ if (logErrors) {
+ log->error(Logr::Notice, msg, "PolicyHit");
+ }
+ }
+ catch (...) {
+ log->error(Logr::Error, msg, "Unexpectec exception");
+ }
+ if (ex) {
+ ++s_resolve_tasks.exceptions;
+ }
+ else {
+ ++s_resolve_tasks.run;
+ }
+}
+
+void runTasks(size_t max, bool logErrors)
+{
+ for (size_t count = 0; count < max; count++) {
+ pdns::ResolveTask task;
+ {
+ auto lock = s_taskQueue.lock();
+ if (lock->queue.empty()) {
+ return;
+ }
+ task = lock->queue.pop();
+ }
+ bool expired = task.run(logErrors);
+ if (expired) {
+ s_taskQueue.lock()->queue.incExpired();
+ }
+ }
+}
+
void runTaskOnce(bool logErrors)
{
pdns::ResolveTask task;
log->error(Logr::Error, "Cannot push task", "qtype unsupported");
return;
}
- pdns::ResolveTask task{qname, qtype, deadline, true, resolve};
- s_taskQueue.lock()->queue.push(std::move(task));
- ++s_almost_expired_tasks.pushed;
+ pdns::ResolveTask task{qname, qtype, deadline, true, resolve, {}};
+ if (s_taskQueue.lock()->queue.push(std::move(task))) {
+ ++s_almost_expired_tasks.pushed;
+ }
}
void pushResolveTask(const DNSName& qname, uint16_t qtype, time_t now, time_t deadline)
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) {
- lock->queue.push(std::move(task));
- ++s_resolve_tasks.pushed;
+ if (lock->queue.push(std::move(task))) {
+ ++s_resolve_tasks.pushed;
+ }
+ }
+}
+
+bool pushTryDoTTask(const DNSName& qname, uint16_t qtype, const ComboAddress& ip, time_t deadline)
+{
+ if (SyncRes::isUnsupported(qtype)) {
+ auto log = g_slog->withName("taskq")->withValues("name", Logging::Loggable(qname), "qtype", Logging::Loggable(QType(qtype).toString()));
+ log->error(Logr::Error, "Cannot push task", "qtype unsupported");
+ return false;
+ }
+
+ pdns::ResolveTask task{qname, qtype, deadline, false, tryDoT, ip};
+ bool pushed = s_taskQueue.lock()->queue.push(std::move(task));
+ if (pushed) {
+ ++s_almost_expired_tasks.pushed;
}
+ return pushed;
}
uint64_t getTaskPushes()
#include <time.h>
class DNSName;
+union ComboAddress;
+
namespace pdns
{
struct ResolveTask;
}
+void runTasks(size_t max, bool logErrors);
void runTaskOnce(bool logErrors);
void pushAlmostExpiredTask(const DNSName& qname, uint16_t qtype, time_t deadline);
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);
void taskQueueClear();
pdns::ResolveTask taskQueuePop();
namespace pdns
{
-void TaskQueue::push(ResolveTask&& task)
+bool TaskQueue::push(ResolveTask&& task)
{
// Insertion fails if it's already there, no problem since we're already scheduled
- auto result = d_queue.insert(std::move(task));
- if (result.second) {
+ auto result = d_queue.insert(std::move(task)).second;
+ if (result) {
d_pushes++;
}
+ return result;
}
ResolveTask TaskQueue::pop()
}
} /* namespace pdns */
+
+namespace boost
+{
+size_t hash_value(const ComboAddress& a)
+{
+ return ComboAddress::addressOnlyHash()(a);
+}
+}
#include <sys/time.h>
#include <thread>
+union ComboAddress;
+namespace boost
+{
+ size_t hash_value(const ComboAddress&);
+}
+
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/key_extractors.hpp>
#include <boost/multi_index/tag.hpp>
#include "dnsname.hh"
+#include "iputils.hh"
#include "qtype.hh"
+
namespace pdns
{
using namespace ::boost::multi_index;
bool d_refreshMode; // Whether to run this task in regular mode (false) or in the mode that refreshes almost expired tasks
// Use a function pointer as comparing std::functions is a nuisance
void (*d_func)(const struct timeval& now, bool logErrors, const ResolveTask& task);
+ ComboAddress d_ip;
bool operator<(const ResolveTask& a) const
{
- 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);
+ 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);
}
+
bool run(bool logErrors);
};
return d_queue.size();
}
- void push(ResolveTask&& task);
+ bool push(ResolveTask&& task);
ResolveTask pop();
uint64_t getPushes()
composite_key<ResolveTask,
member<ResolveTask, DNSName, &ResolveTask::d_qname>,
member<ResolveTask, uint16_t, &ResolveTask::d_qtype>,
- member<ResolveTask, bool, &ResolveTask::d_refreshMode>>>,
+ member<ResolveTask, bool, &ResolveTask::d_refreshMode>,
+ member<ResolveTask, void (*)(const struct timeval& now, bool logErrors, const ResolveTask& task), &ResolveTask::d_func>,
+ member<ResolveTask, ComboAddress, &ResolveTask::d_ip>>>,
sequenced<tag<SequencedTag>>>>
queue_t;
};
}
+
static LockGuarded<fails_t<ComboAddress>> s_fails;
static LockGuarded<fails_t<DNSName>> s_nonresolving;
+struct DoTStatus
+{
+ enum Status : uint8_t { Unknown, Busy, Bad, Good };
+ DNSName d_auth;
+ time_t d_last;
+ Status d_status{Unknown};
+ std::string toString() const
+ {
+ const std::string n[] = { "Unknown", "Busy", "Bad", "Good"};
+ unsigned int v = static_cast<unsigned int>(d_status);
+ return v >= sizeof(n) ? "?" : n[v];
+ }
+};
+
+class DotMap: public map<ComboAddress, DoTStatus>
+{
+public:
+ uint64_t d_numBusy{0};
+};
+LockGuarded<DotMap> s_dotMap;
+
+const time_t dotFailWait = 24 * 3600;
+const time_t dotSuccessWait = 3 * 24 * 3600;
+
unsigned int SyncRes::s_maxnegttl;
unsigned int SyncRes::s_maxbogusttl;
unsigned int SyncRes::s_maxcachettl;
char tmp[26];
fprintf(fp.get(), "%s\t%" PRIu64 "\t%s\n", i.d_domain.toString().c_str(), i.d_count, timestamp(i.d_ttd, tmp, sizeof(tmp)));
}
+ return count;
+}
+
+uint64_t SyncRes::doDumpDoTProbeMap(int fd)
+{
+ int newfd = dup(fd);
+ if (newfd == -1) {
+ return 0;
+ }
+ auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fdopen(newfd, "w"), fclose);
+ if (!fp) {
+ close(newfd);
+ return 0;
+ }
+ fprintf(fp.get(), "; DoT probing map follows\n");
+ fprintf(fp.get(), "; ip\tstatus\ttimestamp\n");
+ uint64_t count=0;
+
+ // We get a copy, so the I/O does not need to happen while holding the lock
+ for (const auto& i : *s_dotMap.lock()) {
+ count++;
+ char tmp[26];
+ fprintf(fp.get(), "%s\t%s\t%s\t%s", i.first.toString().c_str(), i.second.d_auth.toString().c_str(), i.second.toString().c_str(), ctime_r(&i.second.d_last, tmp));
+ }
return count;
}
return done;
}
+
+static void submitTryDotTask(ComboAddress address, const DNSName& auth, time_t now)
+{
+ if (address.getPort() == 853) {
+ return;
+ }
+ address.setPort(853);
+ auto lock = s_dotMap.lock();
+ auto it = lock->emplace(address, DoTStatus{auth, now}).first;
+ if (it->second.d_status == DoTStatus::Busy) {
+ cerr << "Already probing DoT for " << address.toString() << endl << endl;
+ return;
+ }
+ if (it->second.d_status == DoTStatus::Bad && it->second.d_last + dotFailWait > now) {
+ cerr << "Bad DoT for " << address.toString()<< endl << endl;
+ return;
+ }
+ if (it->second.d_status == DoTStatus::Good && it->second.d_last + dotSuccessWait > now) {
+ cerr << "Good DoT for " << address.toString() << endl << endl;
+ return;
+ }
+ it->second.d_last = now;
+ bool pushed = pushTryDoTTask(auth, QType::SOA, address, std::numeric_limits<time_t>::max());
+ if (pushed) {
+ cerr << "Probing DoT for " << address.toString() << endl << endl;
+ it->second.d_status = DoTStatus::Busy;
+ ++lock->d_numBusy;
+ } else {
+ cerr << "NOT Probing DoT for " << address.toString() << endl << endl;
+ }
+}
+
+static bool shouldDoDoT(ComboAddress address, time_t now)
+{
+ address.setPort(853);
+ auto lock = s_dotMap.lock();
+ if (lock->d_numBusy > 1) {
+ cerr << "Busy DoT for " << address.toString() << endl << endl;
+ return false;
+ }
+ auto it = lock->find(address);
+ if (it == lock->end()) {
+ return false;
+ }
+ if (it->second.d_status == DoTStatus::Good && it->second.d_last + dotSuccessWait > now) {
+ cerr << "Doing DoT for " << address.toString() << endl << endl;
+ return true;
+ }
+ return false;
+}
+
+bool SyncRes::tryDoT(const DNSName& qname, const QType qtype, const DNSName& auth, const DNSName& nsName, ComboAddress address, time_t now)
+{
+ LWResult lwr;
+ bool truncated;
+ bool spoofed;
+ boost::optional<Netmask> nm;
+ address.setPort(853);
+ bool ok = doResolveAtThisIP("", qname, qtype, lwr, nm, auth, false, false, nsName, address, true, true, truncated, spoofed);
+ ok = ok && lwr.d_rcode == RCode::NoError && lwr.d_records.size() > 0;
+
+ auto lock = s_dotMap.lock();
+ --lock->d_numBusy;
+ auto it = lock->find(address);
+ if (it != lock->end()) {
+ cerr << "DoT for " << address.toString() << ": " << ok << endl << endl;
+ it->second.d_status = ok ? DoTStatus::Good : DoTStatus::Bad;
+ }
+ return ok;
+}
+
bool SyncRes::doResolveAtThisIP(const std::string& prefix, const DNSName& qname, const QType qtype, LWResult& lwr, boost::optional<Netmask>& ednsmask, const DNSName& auth, bool const sendRDQuery, const bool wasForwarded, const DNSName& nsName, const ComboAddress& remoteIP, bool doTCP, bool doDoT, bool& truncated, bool& spoofed)
{
bool chained = false;
if (SyncRes::s_dot_to_port_853 && remoteIP->getPort() == 853) {
doDoT = true;
}
+ if (!doDoT && shouldDoDoT(*remoteIP, d_now.tv_sec)) {
+ doDoT = true;
+ }
bool forceTCP = doDoT;
+ if (!doDoT) {
+ submitTryDotTask(*remoteIP, auth, d_now.tv_sec);
+ }
if (!forceTCP) {
gotAnswer = doResolveAtThisIP(prefix, qname, qtype, lwr, ednsmask, auth, sendRDQuery, wasForwarded,
tns->first, *remoteIP, false, false, truncated, spoofed);
static uint64_t doDumpFailedServers(int fd);
static uint64_t doDumpNonResolvingNS(int fd);
static uint64_t doDumpSavedParentNSSets(int fd);
+ static uint64_t doDumpDoTProbeMap(int fd);
static int getRootNS(struct timeval now, asyncresolve_t asyncCallback, unsigned int depth);
static void addDontQuery(const std::string& mask)
explicit SyncRes(const struct timeval& now);
int beginResolve(const DNSName &qname, QType qtype, QClass qclass, vector<DNSRecord>&ret, unsigned int depth = 0);
+ bool tryDoT(const DNSName& qname, QType qtype, const DNSName& auth, const DNSName& nsName, ComboAddress address, time_t);
void setId(int id)
{