#include "rec-main.hh"
-#include <netdb.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#ifdef HAVE_BOOST_CONTAINER_FLAT_SET_HPP
-#include <boost/container/flat_set.hpp>
-#endif
-#include "ws-recursor.hh"
-#include <thread>
-#include "threadname.hh"
-#include "recpacketcache.hh"
-#include "utility.hh"
-#include "dns_random.hh"
-#ifdef HAVE_LIBSODIUM
-#include <sodium.h>
-#endif
-#include "opensslsigners.hh"
-#include <iostream>
-#include <errno.h>
-#include <boost/static_assert.hpp>
-#include <map>
-#include <set>
-#include "recursor_cache.hh"
-#include "cachecleaner.hh"
-#include <stdio.h>
-#include <signal.h>
-#include <stdlib.h>
-#include "misc.hh"
-#include "mtasker.hh"
-#include <utility>
#include "arguments.hh"
-#include "syncres.hh"
-#include <fcntl.h>
-#include <fstream>
-#include "sortlist.hh"
-#include "sstuff.hh"
-#include <boost/any.hpp>
-#include <boost/tuple/tuple.hpp>
-#include <boost/tuple/tuple_comparison.hpp>
-#include <boost/function.hpp>
-#include <boost/algorithm/string.hpp>
-#ifdef MALLOC_TRACE
-#include "malloctrace.hh"
-#endif
-#include <netinet/tcp.h>
-#include "aggressive_nsec.hh"
-#include "capabilities.hh"
-#include "dnsparser.hh"
-#include "dnswriter.hh"
-#include "dnsrecords.hh"
-#include "zoneparser-tng.hh"
-#include "rec_channel.hh"
-#include "logger.hh"
-#include "logging.hh"
-#include "iputils.hh"
-#include "mplexer.hh"
-#include "config.h"
-#include "lua-recursor4.hh"
-#include "version.hh"
-#include "responsestats.hh"
-#include "secpoll-recursor.hh"
-#include "dnsname.hh"
-#include "filterpo.hh"
-#include "rpzloader.hh"
-#include "validate-recursor.hh"
-#include "rec-lua-conf.hh"
-#include "ednsoptions.hh"
+#include "dns_random.hh"
#include "ednsextendederror.hh"
#include "ednspadding.hh"
-#include "gettime.hh"
-#include "proxy-protocol.hh"
-#include "pubsuffix.hh"
-#include "shuffle.hh"
-#ifdef NOD_ENABLED
-#include "nod.hh"
-#include "logging.hh"
-#endif /* NOD_ENABLED */
#include "query-local-address.hh"
-#include "rec-tcpout.hh"
-
-#include "rec-snmp.hh"
#include "rec-taskqueue.hh"
+#include "responsestats.hh"
+#include "shuffle.hh"
+#include "validate-recursor.hh"
+#include "xpf.hh"
#ifdef HAVE_SYSTEMD
#include <systemd/sd-daemon.h>
#endif
-#include "namespaces.hh"
-
-#include "uuid-utils.hh"
-#include "rec-protozero.hh"
-
-#include "xpf.hh"
-#include "rec-eventtrace.hh"
+#ifdef NOD_ENABLED
+#include "nod.hh"
+#include "logging.hh"
+#endif /* NOD_ENABLED */
thread_local std::shared_ptr<RecursorLua4> t_pdl;
thread_local unsigned int t_id = 0;
-static thread_local std::shared_ptr<Regex> t_traceRegex;
+thread_local std::shared_ptr<Regex> t_traceRegex;
thread_local std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>> t_protobufServers{nullptr};
-static thread_local uint64_t t_protobufServersGeneration;
thread_local std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>> t_outgoingProtobufServers{nullptr};
-static thread_local uint64_t t_outgoingProtobufServersGeneration;
-#ifdef HAVE_FSTRM
-static thread_local std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>> t_frameStreamServers{nullptr};
-static thread_local uint64_t t_frameStreamServersGeneration;
-#endif /* HAVE_FSTRM */
thread_local std::unique_ptr<MT_t> MT; // the big MTasker
std::unique_ptr<MemRecursorCache> g_recCache;
thread_local std::shared_ptr<NetmaskGroup> t_allowFrom;
thread_local std::shared_ptr<NetmaskGroup> t_allowNotifyFrom;
thread_local std::shared_ptr<notifyset_t> t_allowNotifyFor;
-#ifdef NOD_ENABLED
-thread_local std::shared_ptr<nod::NODDB> t_nodDBp;
-thread_local std::shared_ptr<nod::UniqueResponseDB> t_udrDBp;
-#endif /* NOD_ENABLED */
__thread struct timeval g_now; // timestamp, updated (too) frequently
-// for communicating with our threads
-// effectively readonly after startup
-struct RecThreadInfo
-{
- struct ThreadPipeSet
- {
- int writeToThread{-1};
- int readToThread{-1};
- int writeFromThread{-1};
- int readFromThread{-1};
- int writeQueriesToThread{-1}; // this one is non-blocking
- int readQueriesToThread{-1};
- };
-
- /* FD corresponding to TCP sockets this thread is listening
- on.
- These FDs are also in deferredAdds when we have one
- socket per listener, and in g_deferredAdds instead. */
- std::set<int> tcpSockets;
- /* FD corresponding to listening sockets if we have one socket per
- listener (with reuseport), otherwise all listeners share the
- same FD and g_deferredAdds is then used instead */
- deferredAdd_t deferredAdds;
- struct ThreadPipeSet pipes;
- std::thread thread;
- MT_t* mt{nullptr};
- uint64_t numberOfDistributedQueries{0};
- int exitCode{0};
- /* handle the web server, carbon, statistics and the control channel */
- bool isHandler{false};
- /* accept incoming queries (and distributes them to the workers if pdns-distributes-queries is set) */
- bool isListener{false};
- /* process queries */
- bool isWorker{false};
-};
-
-/* first we have the handler thread, t_id == 0 (some other
- helper threads like SNMP might have t_id == 0 as well)
- then the distributor threads if any
- and finally the workers */
-static std::vector<RecThreadInfo> s_threadInfos;
-/* without reuseport, all listeners share the same sockets */
-static deferredAdd_t g_deferredAdds;
typedef map<int, ComboAddress> listenSocketsAddresses_t; // is shared across all threads right now
-enum class PaddingMode { Always, PaddedQueries };
static listenSocketsAddresses_t g_listenSocketsAddresses; // is shared across all threads right now
static set<int> g_fromtosockets; // listen sockets that use 'sendfromto()' mechanism (without actually using sendfromto())
-static std::atomic<uint32_t> counter;
-static std::shared_ptr<SyncRes::domainmap_t> g_initialDomainMap; // new threads needs this to be setup
-static std::shared_ptr<NetmaskGroup> g_initialAllowFrom; // new thread needs to be setup with this
-static std::shared_ptr<NetmaskGroup> g_initialAllowNotifyFrom; // new threads need this to be setup
-static std::shared_ptr<notifyset_t> g_initialAllowNotifyFor; // new threads need this to be setup
NetmaskGroup g_XPFAcl;
-static NetmaskGroup g_proxyProtocolACL;
NetmaskGroup g_paddingFrom;
-static boost::optional<ComboAddress> g_dns64Prefix{boost::none};
-static DNSName g_dns64PrefixReverse;
size_t g_proxyProtocolMaximumSize;
-static size_t s_maxUDPQueriesPerRound;
-static uint64_t g_latencyStatSize;
-static uint32_t g_disthashseed;
+size_t s_maxUDPQueriesPerRound;
unsigned int g_maxMThreads;
-static unsigned int g_numDistributorThreads;
-static unsigned int g_numWorkerThreads;
unsigned int g_paddingTag;
-static uint16_t g_udpTruncationThreshold;
-static uint16_t g_xpfRRCode{0};
-static PaddingMode g_paddingMode;
-static std::atomic<bool> statsWanted;
+PaddingMode g_paddingMode;
+uint16_t g_udpTruncationThreshold;
std::atomic<bool> g_quiet;
bool g_logCommonErrors;
-static bool g_weDistributeQueries; // if true, 1 or more threads listen on the incoming query sockets and distribute them to workers
bool g_reusePort{false};
bool g_gettagNeedsEDNSOptions{false};
-static time_t g_statisticsInterval;
-static bool g_useIncomingECS;
-static bool g_useKernelTimestamp;
+bool g_useKernelTimestamp;
std::atomic<uint32_t> g_maxCacheEntries, g_maxPacketCacheEntries;
-#ifdef NOD_ENABLED
-static bool g_nodEnabled;
-static DNSName g_nodLookupDomain;
-static bool g_nodLog;
-static SuffixMatchNode g_nodDomainWL;
-static std::string g_nod_pbtag;
-static bool g_udrEnabled;
-static bool g_udrLog;
-static std::string g_udr_pbtag;
-#endif /* NOD_ENABLED */
#ifdef HAVE_BOOST_CONTAINER_FLAT_SET_HPP
-static boost::container::flat_set<uint16_t> s_avoidUdpSourcePorts;
+boost::container::flat_set<uint16_t> s_avoidUdpSourcePorts;
#else
-static std::set<uint16_t> s_avoidUdpSourcePorts;
+std::set<uint16_t> s_avoidUdpSourcePorts;
#endif
-static uint16_t s_minUdpSourcePort;
-static uint16_t s_maxUdpSourcePort;
-static double s_balancingFactor;
-static bool s_addExtendedResolutionDNSErrors;
+uint16_t s_minUdpSourcePort;
+uint16_t s_maxUdpSourcePort;
+double s_balancingFactor;
-RecursorControlChannel s_rcc; // only active in the handler thread
RecursorStats g_stats;
-string s_programname="pdns_recursor";
-string s_pidfname;
bool g_lowercaseOutgoing;
unsigned int g_networkTimeoutMsec;
-unsigned int g_numThreads;
uint16_t g_outgoingEDNSBufsize;
-bool g_logRPZChanges{false};
// Used in Syncres to counts DNSSEC stats for names in a different "universe"
GlobalStateHolder<SuffixMatchNode> g_xdnssec;
GlobalStateHolder<SuffixMatchNode> g_dontThrottleNames;
GlobalStateHolder<NetmaskGroup> g_dontThrottleNetmasks;
GlobalStateHolder<SuffixMatchNode> g_DoTToAuthNames;
+uint64_t g_latencyStatSize;
-#define LOCAL_NETS "127.0.0.0/8, 10.0.0.0/8, 100.64.0.0/10, 169.254.0.0/16, 192.168.0.0/16, 172.16.0.0/12, ::1/128, fc00::/7, fe80::/10"
-#define LOCAL_NETS_INVERSE "!127.0.0.0/8, !10.0.0.0/8, !100.64.0.0/10, !169.254.0.0/16, !192.168.0.0/16, !172.16.0.0/12, !::1/128, !fc00::/7, !fe80::/10"
-// Bad Nets taken from both:
-// http://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml
-// and
-// http://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml
-// where such a network may not be considered a valid destination
-#define BAD_NETS "0.0.0.0/8, 192.0.0.0/24, 192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24, 240.0.0.0/4, ::/96, ::ffff:0:0/96, 100::/64, 2001:db8::/32"
-#define DONT_QUERY LOCAL_NETS ", " BAD_NETS
-
-struct ThreadMSG
-{
- pipefunc_t func;
- bool wantAnswer;
-};
-
-
-ArgvMap &arg()
-{
- static ArgvMap theArg;
- return theArg;
-}
-
-
-static bool isDistributorThread()
-{
- if (t_id == 0) {
- return false;
- }
-
- return g_weDistributeQueries && s_threadInfos.at(t_id).isListener;
-}
-
-static bool isHandlerThread()
-{
- if (t_id == 0) {
- return true;
- }
-
- return s_threadInfos.at(t_id).isHandler;
-}
-
-
-static void handleGenUDPQueryResponse(int fd, FDMultiplexer::funcparam_t& var)
-{
- std::shared_ptr<PacketID> pident = boost::any_cast<std::shared_ptr<PacketID>>(var);
- PacketBuffer resp;
- resp.resize(512);
- ComboAddress fromaddr;
- socklen_t addrlen = sizeof(fromaddr);
-
- ssize_t ret = recvfrom(fd, resp.data(), resp.size(), 0, (sockaddr *)&fromaddr, &addrlen);
- if (fromaddr != pident->remote) {
- g_log<<Logger::Notice<<"Response received from the wrong remote host ("<<fromaddr.toStringWithPort()<<" instead of "<<pident->remote.toStringWithPort()<<"), discarding"<<endl;
-
- }
-
- t_fdm->removeReadFD(fd);
- if(ret >= 0) {
- MT->sendEvent(pident, &resp);
- }
- else {
- PacketBuffer empty;
- MT->sendEvent(pident, &empty);
- // cerr<<"Had some kind of error: "<<ret<<", "<<stringerror()<<endl;
- }
-}
-
-PacketBuffer GenUDPQueryResponse(const ComboAddress& dest, const string& query)
-{
- Socket s(dest.sin4.sin_family, SOCK_DGRAM);
- s.setNonBlocking();
- ComboAddress local = pdns::getQueryLocalAddress(dest.sin4.sin_family, 0);
-
- s.bind(local);
- s.connect(dest);
- s.send(query);
-
- std::shared_ptr<PacketID> pident = std::make_shared<PacketID>();
- pident->fd = s.getHandle();
- pident->remote = dest;
- pident->type = 0;
- t_fdm->addReadFD(s.getHandle(), handleGenUDPQueryResponse, pident);
-
- PacketBuffer data;
- int ret=MT->waitEvent(pident, &data, g_networkTimeoutMsec);
-
- if (!ret || ret==-1) { // timeout
- t_fdm->removeReadFD(s.getHandle());
- }
- else if(data.empty()) {// error, EOF or other
- // we could special case this
- return data;
- }
- return data;
-}
-
-static void handleUDPServerResponse(int fd, FDMultiplexer::funcparam_t&);
-
-// you can ask this class for a UDP socket to send a query from
-// this socket is not yours, don't even think about deleting it
-// but after you call 'returnSocket' on it, don't assume anything anymore
-class UDPClientSocks
-{
- unsigned int d_numsocks;
-public:
- UDPClientSocks() : d_numsocks(0)
- {
- }
-
- LWResult::Result getSocket(const ComboAddress& toaddr, int* fd)
+LWResult::Result UDPClientSocks::getSocket(const ComboAddress& toaddr, int* fd)
{
*fd = makeClientSocket(toaddr.sin4.sin_family);
if(*fd < 0) { // temporary error - receive exception otherwise
}
// return a socket to the pool, or simply erase it
- void returnSocket(int fd)
+void UDPClientSocks::returnSocket(int fd)
{
try {
t_fdm->removeReadFD(fd);
--d_numsocks;
}
-private:
// returns -1 for errors which might go away, throws for ones that won't
- static int makeClientSocket(int family)
+int UDPClientSocks::makeClientSocket(int family)
{
int ret = socket(family, SOCK_DGRAM, 0); // turns out that setting CLO_EXEC and NONBLOCK from here is not a performance win on Linux (oddly enough)
}
return ret;
}
-};
-static thread_local std::unique_ptr<UDPClientSocks> t_udpclientsocks;
+
+static void handleGenUDPQueryResponse(int fd, FDMultiplexer::funcparam_t& var)
+{
+ std::shared_ptr<PacketID> pident = boost::any_cast<std::shared_ptr<PacketID>>(var);
+ PacketBuffer resp;
+ resp.resize(512);
+ ComboAddress fromaddr;
+ socklen_t addrlen = sizeof(fromaddr);
+
+ ssize_t ret = recvfrom(fd, resp.data(), resp.size(), 0, (sockaddr *)&fromaddr, &addrlen);
+ if (fromaddr != pident->remote) {
+ g_log<<Logger::Notice<<"Response received from the wrong remote host ("<<fromaddr.toStringWithPort()<<" instead of "<<pident->remote.toStringWithPort()<<"), discarding"<<endl;
+
+ }
+
+ t_fdm->removeReadFD(fd);
+ if(ret >= 0) {
+ MT->sendEvent(pident, &resp);
+ }
+ else {
+ PacketBuffer empty;
+ MT->sendEvent(pident, &empty);
+ // cerr<<"Had some kind of error: "<<ret<<", "<<stringerror()<<endl;
+ }
+}
+
+PacketBuffer GenUDPQueryResponse(const ComboAddress& dest, const string& query)
+{
+ Socket s(dest.sin4.sin_family, SOCK_DGRAM);
+ s.setNonBlocking();
+ ComboAddress local = pdns::getQueryLocalAddress(dest.sin4.sin_family, 0);
+
+ s.bind(local);
+ s.connect(dest);
+ s.send(query);
+
+ std::shared_ptr<PacketID> pident = std::make_shared<PacketID>();
+ pident->fd = s.getHandle();
+ pident->remote = dest;
+ pident->type = 0;
+ t_fdm->addReadFD(s.getHandle(), handleGenUDPQueryResponse, pident);
+
+ PacketBuffer data;
+ int ret=MT->waitEvent(pident, &data, g_networkTimeoutMsec);
+
+ if (!ret || ret==-1) { // timeout
+ t_fdm->removeReadFD(s.getHandle());
+ }
+ else if(data.empty()) {// error, EOF or other
+ // we could special case this
+ return data;
+ }
+ return data;
+}
+
+static void handleUDPServerResponse(int fd, FDMultiplexer::funcparam_t&);
+
+
+thread_local std::unique_ptr<UDPClientSocks> t_udpclientsocks;
/* these two functions are used by LWRes */
LWResult::Result asendto(const char *data, size_t len, int flags,
return ret == 0 ? LWResult::Result::Timeout : LWResult::Result::PermanentError;
}
-static void writePid(void)
-{
- if(!::arg().mustDo("write-pid"))
- return;
- ofstream of(s_pidfname.c_str(), std::ios_base::app);
- if(of)
- of<< Utility::getpid() <<endl;
- else {
- int err = errno;
- g_log << Logger::Error << "Writing pid for " << Utility::getpid() << " to " << s_pidfname << " failed: "
- << stringerror(err) << endl;
- }
-}
return "Exception making error message for exception";
}
-void protobufLogQuery(LocalStateHolder<LuaConfigItems>& luaconfsLocal, const boost::uuids::uuid& uniqueId, const ComboAddress& remote, const ComboAddress& local, const Netmask& ednssubnet, bool tcp, uint16_t id, size_t len, const DNSName& qname, uint16_t qtype, uint16_t qclass, const std::unordered_set<std::string>& policyTags, const std::string& requestorId, const std::string& deviceId, const std::string& deviceName, const std::map<std::string, RecursorLua4::MetaValue>& meta)
-{
- if (!t_protobufServers) {
- return;
- }
-
- Netmask requestorNM(remote, remote.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
- ComboAddress requestor = requestorNM.getMaskedNetwork();
- requestor.setPort(remote.getPort());
-
- pdns::ProtoZero::RecMessage m{128, std::string::size_type(policyTags.empty() ? 0 : 64)}; // It's a guess
- m.setType(pdns::ProtoZero::Message::MessageType::DNSQueryType);
- m.setRequest(uniqueId, requestor, local, qname, qtype, qclass, id, tcp ? pdns::ProtoZero::Message::TransportProtocol::TCP : pdns::ProtoZero::Message::TransportProtocol::UDP, len);
- m.setServerIdentity(SyncRes::s_serverID);
- m.setEDNSSubnet(ednssubnet, ednssubnet.isIPv4() ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
- m.setRequestorId(requestorId);
- m.setDeviceId(deviceId);
- m.setDeviceName(deviceName);
-
- if (!policyTags.empty()) {
- m.addPolicyTags(policyTags);
- }
- for (const auto& mit : meta) {
- m.setMeta(mit.first, mit.second.stringVal, mit.second.intVal);
- }
-
- std::string msg(m.finishAndMoveBuf());
- for (auto& server : *t_protobufServers) {
- server->queueData(msg);
- }
-}
-
-void protobufLogResponse(pdns::ProtoZero::RecMessage& message)
-{
- if (!t_protobufServers) {
- return;
- }
-
- std::string msg(message.finishAndMoveBuf());
- for (auto& server : *t_protobufServers) {
- server->queueData(msg);
- }
-}
-
-void protobufLogResponse(const struct dnsheader* dh, LocalStateHolder<LuaConfigItems>& luaconfsLocal,
- const RecursorPacketCache::OptPBData& pbData, const struct timeval& tv,
- bool tcp, const ComboAddress& source, const ComboAddress& destination,
- const EDNSSubnetOpts& ednssubnet,
- const boost::uuids::uuid& uniqueId, const string& requestorId, const string& deviceId,
- const string& deviceName, const std::map<std::string, RecursorLua4::MetaValue>& meta,
- const RecEventTrace& eventTrace)
-{
- pdns::ProtoZero::RecMessage pbMessage(pbData ? pbData->d_message : "", pbData ? pbData->d_response : "", 64, 10); // The extra bytes we are going to add
- // Normally we take the immutable string from the cache and append a few values, but if it's not there (can this happen?)
- // we start with an empty string and append the minimal
- if (!pbData) {
- pbMessage.setType(pdns::ProtoZero::Message::MessageType::DNSResponseType);
- pbMessage.setServerIdentity(SyncRes::s_serverID);
- }
-
- // In response part
- if (g_useKernelTimestamp && tv.tv_sec) {
- pbMessage.setQueryTime(tv.tv_sec, tv.tv_usec);
- }
- else {
- pbMessage.setQueryTime(g_now.tv_sec, g_now.tv_usec);
- }
-
- // In message part
- Netmask requestorNM(source, source.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
- ComboAddress requestor = requestorNM.getMaskedNetwork();
- pbMessage.setMessageIdentity(uniqueId);
- pbMessage.setFrom(requestor);
- pbMessage.setTo(destination);
- pbMessage.setSocketProtocol(tcp ? pdns::ProtoZero::Message::TransportProtocol::TCP : pdns::ProtoZero::Message::TransportProtocol::UDP);
- pbMessage.setId(dh->id);
-
- pbMessage.setTime();
- pbMessage.setEDNSSubnet(ednssubnet.source, ednssubnet.source.isIPv4() ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
- pbMessage.setRequestorId(requestorId);
- pbMessage.setDeviceId(deviceId);
- pbMessage.setDeviceName(deviceName);
- pbMessage.setFromPort(source.getPort());
- pbMessage.setToPort(destination.getPort());
- for (const auto& m : meta) {
- pbMessage.setMeta(m.first, m.second.stringVal, m.second.intVal);
- }
-#ifdef NOD_ENABLED
- if (g_nodEnabled) {
- pbMessage.setNewlyObservedDomain(false);
- }
-#endif
- if (eventTrace.enabled() && SyncRes::s_event_trace_enabled & SyncRes::event_trace_to_pb) {
- pbMessage.addEvents(eventTrace);
- }
- protobufLogResponse(pbMessage);
-}
/**
* Chases the CNAME provided by the PolicyCustom RPZ policy.
return PolicyResult::NoAction;
}
-static std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>> startProtobufServers(const ProtobufExportConfig& config)
-{
- auto result = std::make_shared<std::vector<std::unique_ptr<RemoteLogger>>>();
-
- for (const auto& server : config.servers) {
- try {
- auto logger = make_unique<RemoteLogger>(server, config.timeout, 100*config.maxQueuedEntries, config.reconnectWaitTime, config.asyncConnect);
- logger->setLogQueries(config.logQueries);
- logger->setLogResponses(config.logResponses);
- result->emplace_back(std::move(logger));
- }
- catch(const std::exception& e) {
- g_log<<Logger::Error<<"Error while starting protobuf logger to '"<<server<<": "<<e.what()<<endl;
- }
- catch(const PDNSException& e) {
- g_log<<Logger::Error<<"Error while starting protobuf logger to '"<<server<<": "<<e.reason<<endl;
- }
- }
-
- return result;
-}
-bool checkProtobufExport(LocalStateHolder<LuaConfigItems>& luaconfsLocal)
+#ifdef NOD_ENABLED
+static bool nodCheckNewDomain(const shared_ptr<Logr::Logger>& nodlogger, const DNSName& dname)
{
- if (!luaconfsLocal->protobufExportConfig.enabled) {
- if (t_protobufServers) {
- for (auto& server : *t_protobufServers) {
- server->stop();
- }
- t_protobufServers.reset();
- }
-
- return false;
- }
-
- /* if the server was not running, or if it was running according to a
- previous configuration */
- if (!t_protobufServers ||
- t_protobufServersGeneration < luaconfsLocal->generation) {
-
- if (t_protobufServers) {
- for (auto& server : *t_protobufServers) {
- server->stop();
+ bool ret = false;
+ // First check the (sub)domain isn't ignored for NOD purposes
+ if (!g_nodDomainWL.check(dname)) {
+ // Now check the NODDB (note this is probabilistic so can have FNs/FPs)
+ if (t_nodDBp && t_nodDBp->isNewDomain(dname)) {
+ if (g_nodLog) {
+ // This should probably log to a dedicated log file
+ SLOG(g_log<<Logger::Notice<<"Newly observed domain nod="<<dname<<endl,
+ nodlogger->info(Logr::Notice, "New domain observed"));
}
+ ret = true;
}
- t_protobufServers.reset();
-
- t_protobufServers = startProtobufServers(luaconfsLocal->protobufExportConfig);
- t_protobufServersGeneration = luaconfsLocal->generation;
}
-
- return true;
+ return ret;
}
-static bool checkOutgoingProtobufExport(LocalStateHolder<LuaConfigItems>& luaconfsLocal)
+static void sendNODLookup(const shared_ptr<Logr::Logger>& nodlogger, const DNSName& dname)
{
- if (!luaconfsLocal->outgoingProtobufExportConfig.enabled) {
- if (t_outgoingProtobufServers) {
- for (auto& server : *t_outgoingProtobufServers) {
- server->stop();
- }
- }
- t_outgoingProtobufServers.reset();
-
- return false;
- }
-
- /* if the server was not running, or if it was running according to a
- previous configuration */
- if (!t_outgoingProtobufServers ||
- t_outgoingProtobufServersGeneration < luaconfsLocal->generation) {
-
- if (t_outgoingProtobufServers) {
- for (auto& server : *t_outgoingProtobufServers) {
- server->stop();
- }
- }
- t_outgoingProtobufServers.reset();
-
- t_outgoingProtobufServers = startProtobufServers(luaconfsLocal->outgoingProtobufExportConfig);
- t_outgoingProtobufServersGeneration = luaconfsLocal->generation;
- }
-
- return true;
-}
-
-#ifdef HAVE_FSTRM
-
-static std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>> startFrameStreamServers(const FrameStreamExportConfig& config)
-{
- auto result = std::make_shared<std::vector<std::unique_ptr<FrameStreamLogger>>>();
-
- for (const auto& server : config.servers) {
- try {
- std::unordered_map<string,unsigned> options;
- options["bufferHint"] = config.bufferHint;
- options["flushTimeout"] = config.flushTimeout;
- options["inputQueueSize"] = config.inputQueueSize;
- options["outputQueueSize"] = config.outputQueueSize;
- options["queueNotifyThreshold"] = config.queueNotifyThreshold;
- options["reopenInterval"] = config.reopenInterval;
- FrameStreamLogger *fsl = nullptr;
- try {
- ComboAddress address(server);
- fsl = new FrameStreamLogger(address.sin4.sin_family, address.toStringWithPort(), true, options);
- }
- catch (const PDNSException& e) {
- fsl = new FrameStreamLogger(AF_UNIX, server, true, options);
- }
- fsl->setLogQueries(config.logQueries);
- fsl->setLogResponses(config.logResponses);
- result->emplace_back(fsl);
- }
- catch(const std::exception& e) {
- g_log<<Logger::Error<<"Error while starting dnstap framestream logger to '"<<server<<": "<<e.what()<<endl;
- }
- catch(const PDNSException& e) {
- g_log<<Logger::Error<<"Error while starting dnstap framestream logger to '"<<server<<": "<<e.reason<<endl;
- }
- }
-
- return result;
-}
-
-bool checkFrameStreamExport(LocalStateHolder<LuaConfigItems>& luaconfsLocal)
-{
- if (!luaconfsLocal->frameStreamExportConfig.enabled) {
- if (t_frameStreamServers) {
- // dt's take care of cleanup
- t_frameStreamServers.reset();
- }
-
- return false;
- }
-
- /* if the server was not running, or if it was running according to a
- previous configuration */
- if (!t_frameStreamServers ||
- t_frameStreamServersGeneration < luaconfsLocal->generation) {
-
- if (t_frameStreamServers) {
- // dt's take care of cleanup
- t_frameStreamServers.reset();
- }
-
- t_frameStreamServers = startFrameStreamServers(luaconfsLocal->frameStreamExportConfig);
- t_frameStreamServersGeneration = luaconfsLocal->generation;
- }
-
- return true;
-}
-#endif /* HAVE_FSTRM */
-
-#ifdef NOD_ENABLED
-static bool nodCheckNewDomain(const shared_ptr<Logr::Logger>& nodlogger, const DNSName& dname)
-{
- bool ret = false;
- // First check the (sub)domain isn't ignored for NOD purposes
- if (!g_nodDomainWL.check(dname)) {
- // Now check the NODDB (note this is probabilistic so can have FNs/FPs)
- if (t_nodDBp && t_nodDBp->isNewDomain(dname)) {
- if (g_nodLog) {
- // This should probably log to a dedicated log file
- SLOG(g_log<<Logger::Notice<<"Newly observed domain nod="<<dname<<endl,
- nodlogger->info(Logr::Notice, "New domain observed"));
- }
- ret = true;
- }
- }
- return ret;
-}
-
-static void sendNODLookup(const shared_ptr<Logr::Logger>& nodlogger, const DNSName& dname)
-{
- if (!(g_nodLookupDomain.isRoot())) {
- // Send a DNS A query to <domain>.g_nodLookupDomain
- DNSName qname;
- try {
- qname = dname + g_nodLookupDomain;
+ if (!(g_nodLookupDomain.isRoot())) {
+ // Send a DNS A query to <domain>.g_nodLookupDomain
+ DNSName qname;
+ try {
+ qname = dname + g_nodLookupDomain;
}
catch(const std::range_error &e) {
nodlogger->v(10)->error(Logr::Error, "DNSName too long", "Unable to send NOD lookup");
g_stats.maxMThreadStackUsage = max(MT->getMaxStackUsage(), g_stats.maxMThreadStackUsage.load());
}
-static void makeControlChannelSocket(int processNum=-1)
-{
- string sockname=::arg()["socket-dir"]+"/"+s_programname;
- if(processNum >= 0)
- sockname += "."+std::to_string(processNum);
- sockname+=".controlsocket";
- s_rcc.listen(sockname);
-
- int sockowner = -1;
- int sockgroup = -1;
-
- if (!::arg().isEmpty("socket-group"))
- sockgroup=::arg().asGid("socket-group");
- if (!::arg().isEmpty("socket-owner"))
- sockowner=::arg().asUid("socket-owner");
-
- if (sockgroup > -1 || sockowner > -1) {
- if(chown(sockname.c_str(), sockowner, sockgroup) < 0) {
- unixDie("Failed to chown control socket");
- }
- }
-
- // do mode change if socket-mode is given
- if(!::arg().isEmpty("socket-mode")) {
- mode_t sockmode=::arg().asMode("socket-mode");
- if(chmod(sockname.c_str(), sockmode) < 0) {
- unixDie("Failed to chmod control socket");
- }
- }
-}
void getQNameAndSubnet(const std::string& question, DNSName* dnsname, uint16_t* qtype, uint16_t* qclass,
bool& foundECS, EDNSSubnetOpts* ednssubnet, EDNSOptionViewMap* options,
}
}
-static void makeUDPServerSockets(deferredAdd_t& deferredAdds)
+void makeUDPServerSockets(deferredAdd_t& deferredAdds)
{
int one=1;
vector<string>locals;
}
}
-static void daemonize(void)
-{
- if(fork())
- exit(0); // bye bye
-
- setsid();
-
- int i=open("/dev/null",O_RDWR); /* open stdin */
- if(i < 0)
- g_log<<Logger::Critical<<"Unable to open /dev/null: "<<stringerror()<<endl;
- else {
- dup2(i,0); /* stdin */
- dup2(i,1); /* stderr */
- dup2(i,2); /* stderr */
- close(i);
- }
-}
-
-static void termIntHandler(int)
-{
- doExit();
-}
-
-static void usr1Handler(int)
-{
- statsWanted=true;
-}
-
-static void usr2Handler(int)
-{
- g_quiet= !g_quiet;
- SyncRes::setDefaultLogMode(g_quiet ? SyncRes::LogNone : SyncRes::Log);
- ::arg().set("quiet")=g_quiet ? "" : "no";
-}
-
-static int ratePercentage(uint64_t nom, uint64_t denom)
-{
- if (denom == 0) {
- return 0;
- }
- return round(100.0 * nom / denom);
-}
-
-static void doStats(void)
-{
- static time_t lastOutputTime;
- static uint64_t lastQueryCount;
-
- uint64_t cacheHits = g_recCache->cacheHits;
- uint64_t cacheMisses = g_recCache->cacheMisses;
- uint64_t cacheSize = g_recCache->size();
- auto rc_stats = g_recCache->stats();
- double r = rc_stats.second == 0 ? 0.0 : (100.0 * rc_stats.first / rc_stats.second);
- uint64_t negCacheSize = g_negCache->size();
- auto taskPushes = getTaskPushes();
- auto taskExpired = getTaskExpired();
- auto taskSize = getTaskSize();
-
- if(g_stats.qcounter && (cacheHits + cacheMisses) && SyncRes::s_queries && SyncRes::s_outqueries) {
- g_log<<Logger::Notice<<"stats: "<<g_stats.qcounter<<" questions, "<<
- cacheSize << " cache entries, "<<
- negCacheSize<<" negative entries, "<<
- ratePercentage(cacheHits, cacheHits + cacheMisses)<<"% cache hits"<<endl;
- g_log << Logger::Notice<< "stats: cache contended/acquired " << rc_stats.first << '/' << rc_stats.second << " = " << r << '%' << endl;
-
- g_log<<Logger::Notice<<"stats: throttle map: "
- << broadcastAccFunction<uint64_t>(pleaseGetThrottleSize) <<", ns speeds: "
- << broadcastAccFunction<uint64_t>(pleaseGetNsSpeedsSize)<<", failed ns: "
- << broadcastAccFunction<uint64_t>(pleaseGetFailedServersSize)<<", ednsmap: "
- <<broadcastAccFunction<uint64_t>(pleaseGetEDNSStatusesSize)<<endl;
- g_log<<Logger::Notice<<"stats: outpacket/query ratio "<<ratePercentage(SyncRes::s_outqueries, SyncRes::s_queries)<<"%";
- g_log<<Logger::Notice<<", "<<ratePercentage(SyncRes::s_throttledqueries, SyncRes::s_outqueries+SyncRes::s_throttledqueries)<<"% throttled"<<endl;
- g_log<<Logger::Notice<<"stats: "<<SyncRes::s_tcpoutqueries<<"/"<<SyncRes::s_dotoutqueries << "/" << getCurrentIdleTCPConnections() << " outgoing tcp/dot/idle connections, "<<
- broadcastAccFunction<uint64_t>(pleaseGetConcurrentQueries)<<" queries running, "<<SyncRes::s_outgoingtimeouts<<" outgoing timeouts "<<endl;
-
- uint64_t pcSize = broadcastAccFunction<uint64_t>(pleaseGetPacketCacheSize);
- uint64_t pcHits = broadcastAccFunction<uint64_t>(pleaseGetPacketCacheHits);
- g_log<<Logger::Notice<<"stats: " << pcSize <<
- " packet cache entries, "<< ratePercentage(pcHits, SyncRes::s_queries) << "% packet cache hits"<<endl;
-
- size_t idx = 0;
- for (const auto& threadInfo : s_threadInfos) {
- if(threadInfo.isWorker) {
- g_log<<Logger::Notice<<"stats: thread "<<idx<<" has been distributed "<<threadInfo.numberOfDistributedQueries<<" queries"<<endl;
- ++idx;
- }
- }
-
- g_log<<Logger::Notice<<"stats: tasks pushed/expired/queuesize: " << taskPushes << '/' << taskExpired << '/' << taskSize << endl;
- time_t now = time(0);
- if(lastOutputTime && lastQueryCount && now != lastOutputTime) {
- g_log<<Logger::Notice<<"stats: "<< (SyncRes::s_queries - lastQueryCount) / (now - lastOutputTime) <<" qps (average over "<< (now - lastOutputTime) << " seconds)"<<endl;
- }
- lastOutputTime = now;
- lastQueryCount = SyncRes::s_queries;
- }
- else if(statsWanted)
- g_log<<Logger::Notice<<"stats: no stats yet!"<<endl;
-
- statsWanted=false;
-}
-
-static void houseKeeping(void *)
-{
- static thread_local time_t last_rootupdate, last_secpoll, last_trustAnchorUpdate{0};
- static thread_local struct timeval last_prune;
-
- static thread_local int cleanCounter=0;
- static thread_local bool s_running; // houseKeeping can get suspended in secpoll, and be restarted, which makes us do duplicate work
- static time_t last_RC_prune = 0;
- auto luaconfsLocal = g_luaconfs.getLocal();
-
- if (last_trustAnchorUpdate == 0 && !luaconfsLocal->trustAnchorFileInfo.fname.empty() && luaconfsLocal->trustAnchorFileInfo.interval != 0) {
- // Loading the Lua config file already "refreshed" the TAs
- last_trustAnchorUpdate = g_now.tv_sec + luaconfsLocal->trustAnchorFileInfo.interval * 3600;
- }
-
- try {
- if(s_running) {
- return;
- }
- s_running=true;
-
- runTaskOnce(g_logCommonErrors);
-
- struct timeval now, past;
- Utility::gettimeofday(&now, nullptr);
- past = now;
- past.tv_sec -= 5;
- if (last_prune < past) {
- t_packetCache->doPruneTo(g_maxPacketCacheEntries / (g_numWorkerThreads + g_numDistributorThreads));
-
- time_t limit;
- if(!((cleanCounter++)%40)) { // this is a full scan!
- limit=now.tv_sec-300;
- SyncRes::pruneNSSpeeds(limit);
- }
- limit = now.tv_sec - SyncRes::s_serverdownthrottletime * 10;
- SyncRes::pruneFailedServers(limit);
- limit = now.tv_sec - 2*3600;
- SyncRes::pruneEDNSStatuses(limit);
- SyncRes::pruneThrottledServers();
- SyncRes::pruneNonResolving(now.tv_sec - SyncRes::s_nonresolvingnsthrottletime);
- Utility::gettimeofday(&last_prune, nullptr);
- t_tcp_manager.cleanup(now);
- }
-
- if(isHandlerThread()) {
- if (now.tv_sec - last_RC_prune > 5) {
- g_recCache->doPrune(g_maxCacheEntries);
- g_negCache->prune(g_maxCacheEntries / 10);
- if (g_aggressiveNSECCache) {
- g_aggressiveNSECCache->prune(now.tv_sec);
- }
- last_RC_prune = now.tv_sec;
- }
- // Divide by 12 to get the original 2 hour cycle if s_maxcachettl is default (1 day)
- if (now.tv_sec - last_rootupdate > max(SyncRes::s_maxcachettl / 12, 10U)) {
- int res = SyncRes::getRootNS(g_now, nullptr, 0);
- if (!res) {
- last_rootupdate=now.tv_sec;
- try {
- primeRootNSZones(g_dnssecmode != DNSSECMode::Off, 0);
- }
- catch (const std::exception& e) {
- g_log<<Logger::Error<<"Exception while priming the root NS zones: "<<e.what()<<endl;
- }
- catch (const PDNSException& e) {
- g_log<<Logger::Error<<"Exception while priming the root NS zones: "<<e.reason<<endl;
- }
- catch (const ImmediateServFailException& e) {
- g_log<<Logger::Error<<"Exception while priming the root NS zones: "<<e.reason<<endl;
- }
- catch (const PolicyHitException& e) {
- g_log<<Logger::Error<<"Policy hit while priming the root NS zones"<<endl;
- }
- catch (...)
- {
- g_log<<Logger::Error<<"Exception while priming the root NS zones"<<endl;
- }
- }
- }
-
- if(now.tv_sec - last_secpoll >= 3600) {
- try {
- doSecPoll(&last_secpoll);
- }
- catch (const std::exception& e)
- {
- g_log<<Logger::Error<<"Exception while performing security poll: "<<e.what()<<endl;
- }
- catch (const PDNSException& e)
- {
- g_log<<Logger::Error<<"Exception while performing security poll: "<<e.reason<<endl;
- }
- catch (const ImmediateServFailException &e)
- {
- g_log<<Logger::Error<<"Exception while performing security poll: "<<e.reason<<endl;
- }
- catch (const PolicyHitException& e) {
- g_log<<Logger::Error<<"Policy hit while performing security poll"<<endl;
- }
- catch (...)
- {
- g_log<<Logger::Error<<"Exception while performing security poll"<<endl;
- }
- }
-
- if (!luaconfsLocal->trustAnchorFileInfo.fname.empty() && luaconfsLocal->trustAnchorFileInfo.interval != 0 &&
- g_now.tv_sec - last_trustAnchorUpdate >= (luaconfsLocal->trustAnchorFileInfo.interval * 3600)) {
- g_log<<Logger::Debug<<"Refreshing Trust Anchors from file"<<endl;
- try {
- map<DNSName, dsmap_t> dsAnchors;
- if (updateTrustAnchorsFromFile(luaconfsLocal->trustAnchorFileInfo.fname, dsAnchors)) {
- g_luaconfs.modify([&dsAnchors](LuaConfigItems& lci) {
- lci.dsAnchors = dsAnchors;
- });
- }
- last_trustAnchorUpdate = now.tv_sec;
- } catch (const PDNSException &pe) {
- g_log<<Logger::Error<<"Unable to update Trust Anchors: "<<pe.reason<<endl;
- }
- }
- }
- s_running = false;
- }
- catch (const PDNSException& ae)
- {
- s_running = false;
- g_log<<Logger::Error<<"Fatal error in housekeeping thread: "<<ae.reason<<endl;
- throw;
- }
- catch (...)
- {
- s_running = false;
- g_log<<Logger::Error<<"Uncaught exception in housekeeping thread"<<endl;
- throw;
- }
-}
-
-static void makeThreadPipes()
-{
- auto pipeBufferSize = ::arg().asNum("distribution-pipe-buffer-size");
- if (pipeBufferSize > 0) {
- g_log<<Logger::Info<<"Resizing the buffer of the distribution pipe to "<<pipeBufferSize<<endl;
- }
-
- /* thread 0 is the handler / SNMP, worker threads start at 1 */
- for(unsigned int n = 0; n <= (g_numWorkerThreads + g_numDistributorThreads); ++n) {
- auto& threadInfos = s_threadInfos.at(n);
-
- int fd[2];
- if(pipe(fd) < 0)
- unixDie("Creating pipe for inter-thread communications");
-
- threadInfos.pipes.readToThread = fd[0];
- threadInfos.pipes.writeToThread = fd[1];
-
- // handler thread only gets first pipe, not the others
- if(n==0) {
- continue;
- }
-
- if(pipe(fd) < 0)
- unixDie("Creating pipe for inter-thread communications");
-
- threadInfos.pipes.readFromThread = fd[0];
- threadInfos.pipes.writeFromThread = fd[1];
-
- if(pipe(fd) < 0)
- unixDie("Creating pipe for inter-thread communications");
-
- threadInfos.pipes.readQueriesToThread = fd[0];
- threadInfos.pipes.writeQueriesToThread = fd[1];
-
- if (pipeBufferSize > 0) {
- if (!setPipeBufferSize(threadInfos.pipes.writeQueriesToThread, pipeBufferSize)) {
- int err = errno;
- g_log<<Logger::Warning<<"Error resizing the buffer of the distribution pipe for thread "<<n<<" to "<<pipeBufferSize<<": "<<strerror(err)<<endl;
- auto existingSize = getPipeBufferSize(threadInfos.pipes.writeQueriesToThread);
- if (existingSize > 0) {
- g_log<<Logger::Warning<<"The current size of the distribution pipe's buffer for thread "<<n<<" is "<<existingSize<<endl;
- }
- }
- }
-
- if (!setNonBlocking(threadInfos.pipes.writeQueriesToThread)) {
- unixDie("Making pipe for inter-thread communications non-blocking");
- }
- }
-}
-
-void broadcastFunction(const pipefunc_t& func)
-{
- /* This function might be called by the worker with t_id 0 during startup
- for the initialization of ACLs and domain maps. After that it should only
- be called by the handler. */
-
- if (s_threadInfos.empty() && isHandlerThread()) {
- /* the handler and distributors will call themselves below, but
- during startup we get called while s_threadInfos has not been
- populated yet to update the ACL or domain maps, so we need to
- handle that case.
- */
- func();
- }
-
- unsigned int n = 0;
- for (const auto& threadInfo : s_threadInfos) {
- if(n++ == t_id) {
- func(); // don't write to ourselves!
- continue;
- }
-
- ThreadMSG* tmsg = new ThreadMSG();
- tmsg->func = func;
- tmsg->wantAnswer = true;
- if(write(threadInfo.pipes.writeToThread, &tmsg, sizeof(tmsg)) != sizeof(tmsg)) {
- delete tmsg;
-
- unixDie("write to thread pipe returned wrong size or error");
- }
-
- string* resp = nullptr;
- if(read(threadInfo.pipes.readFromThread, &resp, sizeof(resp)) != sizeof(resp))
- unixDie("read from thread pipe returned wrong size or error");
- if(resp) {
- delete resp;
- resp = nullptr;
- }
- }
-}
static bool trySendingQueryToWorker(unsigned int target, ThreadMSG* tmsg)
{
}
}
-static void handlePipeRequest(int fd, FDMultiplexer::funcparam_t& var)
-{
- ThreadMSG* tmsg = nullptr;
-
- if(read(fd, &tmsg, sizeof(tmsg)) != sizeof(tmsg)) { // fd == readToThread || fd == readQueriesToThread
- unixDie("read from thread pipe returned wrong size or error");
- }
-
- void *resp=0;
- try {
- resp = tmsg->func();
- }
- catch(std::exception& e) {
- if(g_logCommonErrors)
- g_log<<Logger::Error<<"PIPE function we executed created exception: "<<e.what()<<endl; // but what if they wanted an answer.. we send 0
- }
- catch(PDNSException& e) {
- if(g_logCommonErrors)
- g_log<<Logger::Error<<"PIPE function we executed created PDNS exception: "<<e.reason<<endl; // but what if they wanted an answer.. we send 0
- }
- if(tmsg->wantAnswer) {
- const auto& threadInfo = s_threadInfos.at(t_id);
- if(write(threadInfo.pipes.writeFromThread, &resp, sizeof(resp)) != sizeof(resp)) {
- delete tmsg;
- unixDie("write to thread pipe returned wrong size or error");
- }
- }
-
- delete tmsg;
-}
-
-template<class T> void *voider(const boost::function<T*()>& func)
-{
- return func();
-}
-
-static vector<ComboAddress>& operator+=(vector<ComboAddress>&a, const vector<ComboAddress>& b)
-{
- a.insert(a.end(), b.begin(), b.end());
- return a;
-}
-
-static vector<pair<DNSName, uint16_t> >& operator+=(vector<pair<DNSName, uint16_t> >&a, const vector<pair<DNSName, uint16_t> >& b)
-{
- a.insert(a.end(), b.begin(), b.end());
- return a;
-}
-
-/*
- This function should only be called by the handler to gather metrics, wipe the cache,
- reload the Lua script (not the Lua config) or change the current trace regex,
- and by the SNMP thread to gather metrics. */
-template<class T> T broadcastAccFunction(const boost::function<T*()>& func)
-{
- if (!isHandlerThread()) {
- g_log<<Logger::Error<<"broadcastAccFunction has been called by a worker ("<<t_id<<")"<<endl;
- _exit(1);
- }
-
- unsigned int n = 0;
- T ret=T();
- for (const auto& threadInfo : s_threadInfos) {
- if (n++ == t_id) {
- continue;
- }
-
- const auto& tps = threadInfo.pipes;
- ThreadMSG* tmsg = new ThreadMSG();
- tmsg->func = [func]{ return voider<T>(func); };
- tmsg->wantAnswer = true;
-
- if(write(tps.writeToThread, &tmsg, sizeof(tmsg)) != sizeof(tmsg)) {
- delete tmsg;
- unixDie("write to thread pipe returned wrong size or error");
- }
-
- T* resp = nullptr;
- if(read(tps.readFromThread, &resp, sizeof(resp)) != sizeof(resp))
- unixDie("read from thread pipe returned wrong size or error");
-
- if(resp) {
- ret += *resp;
- delete resp;
- resp = nullptr;
- }
- }
- return ret;
-}
-
-template string broadcastAccFunction(const boost::function<string*()>& fun); // explicit instantiation
-template RecursorControlChannel::Answer broadcastAccFunction(const boost::function<RecursorControlChannel::Answer *()>& fun); // explicit instantiation
-template uint64_t broadcastAccFunction(const boost::function<uint64_t*()>& fun); // explicit instantiation
-template vector<ComboAddress> broadcastAccFunction(const boost::function<vector<ComboAddress> *()>& fun); // explicit instantiation
-template vector<pair<DNSName,uint16_t> > broadcastAccFunction(const boost::function<vector<pair<DNSName, uint16_t> > *()>& fun); // explicit instantiation
-template ThreadTimes broadcastAccFunction(const boost::function<ThreadTimes*()>& fun);
-
-static void handleRCC(int fd, FDMultiplexer::funcparam_t& var)
-{
- try {
- FDWrapper clientfd = accept(fd, nullptr, nullptr);
- if (clientfd == -1) {
- throw PDNSException("accept failed");
- }
- string msg = s_rcc.recv(clientfd).d_str;
- g_log << Logger::Info << "Received rec_control command '" << msg << "' via controlsocket" << endl;
-
- RecursorControlParser rcp;
- RecursorControlParser::func_t* command;
- auto answer = rcp.getAnswer(clientfd, msg, &command);
-
- s_rcc.send(clientfd, answer);
- command();
- }
- catch(const std::exception& e) {
- g_log<<Logger::Error<<"Error dealing with control socket request: "<<e.what()<<endl;
- }
- catch(const PDNSException& ae) {
- g_log<<Logger::Error<<"Error dealing with control socket request: "<<ae.reason<<endl;
- }
-}
// resend event to everybody chained onto it
}
}
-static FDMultiplexer* getMultiplexer()
-{
- FDMultiplexer* ret;
- for(const auto& i : FDMultiplexer::getMultiplexerMap()) {
- try {
- ret=i.second();
- return ret;
- }
- catch(FDMultiplexerException &fe) {
- g_log<<Logger::Error<<"Non-fatal error initializing possible multiplexer ("<<fe.what()<<"), falling back"<<endl;
- }
- catch(...) {
- g_log<<Logger::Error<<"Non-fatal error initializing possible multiplexer"<<endl;
- }
- }
- g_log<<Logger::Error<<"No working multiplexer found!"<<endl;
- _exit(1);
-}
-
-
-static RecursorControlChannel::Answer* doReloadLuaScript()
-{
- string fname= ::arg()["lua-dns-script"];
- try {
- if(fname.empty()) {
- t_pdl.reset();
- g_log<<Logger::Info<<t_id<<" Unloaded current lua script"<<endl;
- return new RecursorControlChannel::Answer{0, string("unloaded\n")};
- }
- else {
- t_pdl = std::make_shared<RecursorLua4>();
- int err = t_pdl->loadFile(fname);
- if (err != 0) {
- string msg = std::to_string(t_id) + " Retaining current script, could not read '" + fname + "': " + stringerror(err);
- g_log<<Logger::Error<<msg<<endl;
- return new RecursorControlChannel::Answer{1, msg + "\n"};
- }
- }
- }
- catch(std::exception& e) {
- g_log<<Logger::Error<<t_id<<" Retaining current script, error from '"<<fname<<"': "<< e.what() <<endl;
- return new RecursorControlChannel::Answer{1, string("retaining current script, error from '"+fname+"': "+e.what()+"\n")};
- }
-
- g_log<<Logger::Warning<<t_id<<" (Re)loaded lua script from '"<<fname<<"'"<<endl;
- return new RecursorControlChannel::Answer{0, string("(re)loaded '"+fname+"'\n")};
-}
-
-RecursorControlChannel::Answer doQueueReloadLuaScript(vector<string>::const_iterator begin, vector<string>::const_iterator end)
-{
- if(begin != end)
- ::arg().set("lua-dns-script") = *begin;
-
- return broadcastAccFunction<RecursorControlChannel::Answer>(doReloadLuaScript);
-}
-
-static string* pleaseUseNewTraceRegex(const std::string& newRegex)
-try
-{
- if(newRegex.empty()) {
- t_traceRegex.reset();
- return new string("unset\n");
- }
- else {
- t_traceRegex = std::make_shared<Regex>(newRegex);
- return new string("ok\n");
- }
-}
-catch(PDNSException& ae)
-{
- return new string(ae.reason+"\n");
-}
-
-string doTraceRegex(vector<string>::const_iterator begin, vector<string>::const_iterator end)
-{
- return broadcastAccFunction<string>([=]{ return pleaseUseNewTraceRegex(begin!=end ? *begin : ""); });
-}
-
-static void checkLinuxIPv6Limits()
-{
-#ifdef __linux__
- string line;
- if(readFileIfThere("/proc/sys/net/ipv6/route/max_size", &line)) {
- int lim=std::stoi(line);
- if(lim < 16384) {
- g_log<<Logger::Error<<"If using IPv6, please raise sysctl net.ipv6.route.max_size, currently set to "<<lim<<" which is < 16384"<<endl;
- }
- }
-#endif
-}
-static void checkOrFixFDS()
-{
- unsigned int availFDs=getFilenumLimit();
- unsigned int wantFDs = g_maxMThreads * g_numWorkerThreads +25; // even healthier margin then before
- wantFDs += g_numWorkerThreads * TCPOutConnectionManager::s_maxIdlePerThread;
-
- if(wantFDs > availFDs) {
- unsigned int hardlimit= getFilenumLimit(true);
- if(hardlimit >= wantFDs) {
- setFilenumLimit(wantFDs);
- g_log<<Logger::Warning<<"Raised soft limit on number of filedescriptors to "<<wantFDs<<" to match max-mthreads and threads settings"<<endl;
- }
- else {
- int newval = (hardlimit - 25 - TCPOutConnectionManager::s_maxIdlePerThread) / g_numWorkerThreads;
- g_log<<Logger::Warning<<"Insufficient number of filedescriptors available for max-mthreads*threads setting! ("<<hardlimit<<" < "<<wantFDs<<"), reducing max-mthreads to "<<newval<<endl;
- g_maxMThreads = newval;
- setFilenumLimit(hardlimit);
- }
- }
-}
-
-static void* recursorThread(unsigned int tid, const string& threadName);
-
-static void* pleaseSupplantAllowFrom(std::shared_ptr<NetmaskGroup> ng)
-{
- t_allowFrom = ng;
- return nullptr;
-}
-
-static void* pleaseSupplantAllowNotifyFrom(std::shared_ptr<NetmaskGroup> ng)
-{
- t_allowNotifyFrom = ng;
- return nullptr;
-}
-
-void* pleaseSupplantAllowNotifyFor(std::shared_ptr<notifyset_t> ns)
-{
- t_allowNotifyFor = ns;
- return nullptr;
-}
-
-int g_argc;
-char** g_argv;
-
-static std::shared_ptr<NetmaskGroup> parseACL(const std::string& aclFile, const std::string& aclSetting)
-{
- auto result = std::make_shared<NetmaskGroup>();
-
- if(!::arg()[aclFile].empty()) {
- string line;
- ifstream ifs(::arg()[aclFile].c_str());
- if(!ifs) {
- throw runtime_error("Could not open '"+::arg()[aclFile]+"': "+stringerror());
- }
-
- string::size_type pos;
- while(getline(ifs,line)) {
- pos=line.find('#');
- if(pos!=string::npos)
- line.resize(pos);
- boost::trim(line);
- if(line.empty())
- continue;
-
- result->addMask(line);
- }
- g_log<<Logger::Info<<"Done parsing "<<result->size()<<" "<<aclSetting<<" ranges from file '"<<::arg()[aclFile]<<"' - overriding '"<<aclSetting<<"' setting"<<endl;
-
- return result;
- }
- else if(!::arg()[aclSetting].empty()) {
- vector<string> ips;
- stringtok(ips, ::arg()[aclSetting], ", ");
-
- g_log<<Logger::Info<<aclSetting<<": ";
- for(vector<string>::const_iterator i = ips.begin(); i!= ips.end(); ++i) {
- result->addMask(*i);
- if(i!=ips.begin())
- g_log<<Logger::Info<<", ";
- g_log<<Logger::Info<<*i;
- }
- g_log<<Logger::Info<<endl;
-
- return result;
- }
-
- return nullptr;
-}
-
-void parseACLs()
-{
- static bool l_initialized;
-
- if(l_initialized) { // only reload configuration file on second call
- string configname=::arg()["config-dir"]+"/recursor.conf";
- if(::arg()["config-name"]!="") {
- configname=::arg()["config-dir"]+"/recursor-"+::arg()["config-name"]+".conf";
- }
- cleanSlashes(configname);
-
- if(!::arg().preParseFile(configname.c_str(), "allow-from-file"))
- throw runtime_error("Unable to re-parse configuration file '"+configname+"'");
- ::arg().preParseFile(configname.c_str(), "allow-from", LOCAL_NETS);
-
- if(!::arg().preParseFile(configname.c_str(), "allow-notify-from-file"))
- throw runtime_error("Unable to re-parse configuration file '"+configname+"'");
- ::arg().preParseFile(configname.c_str(), "allow-notify-from");
-
- ::arg().preParseFile(configname.c_str(), "include-dir");
- ::arg().preParse(g_argc, g_argv, "include-dir");
-
- // then process includes
- std::vector<std::string> extraConfigs;
- ::arg().gatherIncludes(extraConfigs);
-
- for(const std::string& fn : extraConfigs) {
- if(!::arg().preParseFile(fn.c_str(), "allow-from-file", ::arg()["allow-from-file"]))
- throw runtime_error("Unable to re-parse configuration file include '"+fn+"'");
- if(!::arg().preParseFile(fn.c_str(), "allow-from", ::arg()["allow-from"]))
- throw runtime_error("Unable to re-parse configuration file include '"+fn+"'");
-
- if(!::arg().preParseFile(fn.c_str(), "allow-notify-from-file", ::arg()["allow-notify-from-file"]))
- throw runtime_error("Unable to re-parse configuration file include '"+fn+"'");
- if(!::arg().preParseFile(fn.c_str(), "allow-notify-from", ::arg()["allow-notify-from"]))
- throw runtime_error("Unable to re-parse configuration file include '"+fn+"'");
- }
-
- ::arg().preParse(g_argc, g_argv, "allow-from-file");
- ::arg().preParse(g_argc, g_argv, "allow-from");
-
- ::arg().preParse(g_argc, g_argv, "allow-notify-from-file");
- ::arg().preParse(g_argc, g_argv, "allow-notify-from");
- }
-
- auto allowFrom = parseACL("allow-from-file", "allow-from");
-
- if(allowFrom->size() == 0) {
- if(::arg()["local-address"]!="127.0.0.1" && ::arg().asNum("local-port")==53)
- g_log<<Logger::Warning<<"WARNING: Allowing queries from all IP addresses - this can be a security risk!"<<endl;
- allowFrom = nullptr;
- }
-
- g_initialAllowFrom = allowFrom;
- broadcastFunction([=]{ return pleaseSupplantAllowFrom(allowFrom); });
-
- auto allowNotifyFrom = parseACL("allow-notify-from-file", "allow-notify-from");
-
- g_initialAllowNotifyFrom = allowNotifyFrom;
- broadcastFunction([=]{ return pleaseSupplantAllowNotifyFrom(allowNotifyFrom); });
-
- l_initialized = true;
-}
-
-static std::map<unsigned int, std::set<int> > parseCPUMap()
-{
- std::map<unsigned int, std::set<int> > result;
-
- const std::string value = ::arg()["cpu-map"];
-
- if (!value.empty() && !isSettingThreadCPUAffinitySupported()) {
- g_log<<Logger::Warning<<"CPU mapping requested but not supported, skipping"<<endl;
- return result;
- }
-
- std::vector<std::string> parts;
-
- stringtok(parts, value, " \t");
-
- for(const auto& part : parts) {
- if (part.find('=') == string::npos)
- continue;
-
- try {
- auto headers = splitField(part, '=');
- boost::trim(headers.first);
- boost::trim(headers.second);
-
- unsigned int threadId = pdns_stou(headers.first);
- std::vector<std::string> cpus;
-
- stringtok(cpus, headers.second, ",");
-
- for(const auto& cpu : cpus) {
- int cpuId = std::stoi(cpu);
-
- result[threadId].insert(cpuId);
- }
- }
- catch(const std::exception& e) {
- g_log<<Logger::Error<<"Error parsing cpu-map entry '"<<part<<"': "<<e.what()<<endl;
- }
- }
-
- return result;
-}
-
-static void setCPUMap(const std::map<unsigned int, std::set<int> >& cpusMap, unsigned int n, pthread_t tid)
-{
- const auto& cpuMapping = cpusMap.find(n);
- if (cpuMapping != cpusMap.cend()) {
- int rc = mapThreadToCPUList(tid, cpuMapping->second);
- if (rc == 0) {
- g_log<<Logger::Info<<"CPU affinity for worker "<<n<<" has been set to CPU map:";
- for (const auto cpu : cpuMapping->second) {
- g_log<<Logger::Info<<" "<<cpu;
- }
- g_log<<Logger::Info<<endl;
- }
- else {
- g_log<<Logger::Warning<<"Error setting CPU affinity for worker "<<n<<" to CPU map:";
- for (const auto cpu : cpuMapping->second) {
- g_log<<Logger::Info<<" "<<cpu;
- }
- g_log<<Logger::Info<<strerror(rc)<<endl;
- }
- }
-}
-
-#ifdef NOD_ENABLED
-static void setupNODThread()
-{
- if (g_nodEnabled) {
- uint32_t num_cells = ::arg().asNum("new-domain-db-size");
- t_nodDBp = std::make_shared<nod::NODDB>(num_cells);
- try {
- t_nodDBp->setCacheDir(::arg()["new-domain-history-dir"]);
- }
- catch (const PDNSException& e) {
- g_log<<Logger::Error<<"new-domain-history-dir (" << ::arg()["new-domain-history-dir"] << ") is not readable or does not exist"<<endl;
- _exit(1);
- }
- if (!t_nodDBp->init()) {
- g_log<<Logger::Error<<"Could not initialize domain tracking"<<endl;
- _exit(1);
- }
- std::thread t(nod::NODDB::startHousekeepingThread, t_nodDBp, std::this_thread::get_id());
- t.detach();
- g_nod_pbtag = ::arg()["new-domain-pb-tag"];
- }
- if (g_udrEnabled) {
- uint32_t num_cells = ::arg().asNum("unique-response-db-size");
- t_udrDBp = std::make_shared<nod::UniqueResponseDB>(num_cells);
- try {
- t_udrDBp->setCacheDir(::arg()["unique-response-history-dir"]);
- }
- catch (const PDNSException& e) {
- g_log<<Logger::Error<<"unique-response-history-dir (" << ::arg()["unique-response-history-dir"] << ") is not readable or does not exist"<<endl;
- _exit(1);
- }
- if (!t_udrDBp->init()) {
- g_log<<Logger::Error<<"Could not initialize unique response tracking"<<endl;
- _exit(1);
- }
- std::thread t(nod::UniqueResponseDB::startHousekeepingThread, t_udrDBp, std::this_thread::get_id());
- t.detach();
- g_udr_pbtag = ::arg()["unique-response-pb-tag"];
- }
-}
-
-static void parseNODIgnorelist(const std::string& wlist)
-{
- vector<string> parts;
- stringtok(parts, wlist, ",; ");
- for(const auto& a : parts) {
- g_nodDomainWL.add(DNSName(a));
- }
-}
-
-static void setupNODGlobal()
-{
- // Setup NOD subsystem
- g_nodEnabled = ::arg().mustDo("new-domain-tracking");
- g_nodLookupDomain = DNSName(::arg()["new-domain-lookup"]);
- g_nodLog = ::arg().mustDo("new-domain-log");
- parseNODIgnorelist(::arg()["new-domain-whitelist"]);
- parseNODIgnorelist(::arg()["new-domain-ignore-list"]);
-
- // Setup Unique DNS Response subsystem
- g_udrEnabled = ::arg().mustDo("unique-response-tracking");
- g_udrLog = ::arg().mustDo("unique-response-log");
-}
-#endif /* NOD_ENABLED */
-
-static void checkSocketDir(void)
-{
- struct stat st;
- string dir(::arg()["socket-dir"]);
- string msg;
-
- if (stat(dir.c_str(), &st) == -1) {
- msg = "it does not exist or cannot access";
- }
- else if (!S_ISDIR(st.st_mode)) {
- msg = "it is not a directory";
- }
- else if (access(dir.c_str(), R_OK | W_OK | X_OK) != 0) {
- msg = "cannot read, write or search";
- } else {
- return;
- }
- g_log << Logger::Error << "Problem with socket directory " << dir << ": " << msg << "; see https://docs.powerdns.com/recursor/upgrade.html#x-to-4-3-0" << endl;
- _exit(1);
-}
-
-static uint64_t* pleaseWipePacketCache(const DNSName& canon, bool subtree, uint16_t qtype)
-{
- return new uint64_t(t_packetCache->doWipePacketCache(canon, qtype, subtree));
-}
-
-struct WipeCacheResult wipeCaches(const DNSName& canon, bool subtree, uint16_t qtype)
-{
- struct WipeCacheResult res;
-
- try {
- res.record_count = g_recCache->doWipeCache(canon, subtree, qtype);
- res.packet_count = broadcastAccFunction<uint64_t>([=]{ return pleaseWipePacketCache(canon, subtree, qtype);});
- res.negative_record_count = g_negCache->wipe(canon, subtree);
- if (g_aggressiveNSECCache) {
- g_aggressiveNSECCache->removeZoneInfo(canon, subtree);
- }
- }
- catch (const std::exception& e) {
- g_log<<Logger::Warning<<", failed: "<<e.what()<<endl;
- }
-
- return res;
-}
-
-static int serviceMain(int argc, char*argv[])
-{
- int ret = EXIT_SUCCESS;
-
- g_slogStructured = ::arg().mustDo("structured-logging");
-
- g_log.setName(s_programname);
- g_log.disableSyslog(::arg().mustDo("disable-syslog"));
- g_log.setTimestamps(::arg().mustDo("log-timestamp"));
-
- if(!::arg()["logging-facility"].empty()) {
- int val=logFacilityToLOG(::arg().asNum("logging-facility") );
- if(val >= 0)
- g_log.setFacility(val);
- else
- g_log<<Logger::Error<<"Unknown logging facility "<<::arg().asNum("logging-facility") <<endl;
- }
-
- showProductVersion();
-
- g_disthashseed=dns_random(0xffffffff);
-
- checkLinuxIPv6Limits();
- try {
- pdns::parseQueryLocalAddress(::arg()["query-local-address"]);
- }
- catch(std::exception& e) {
- g_log<<Logger::Error<<"Assigning local query addresses: "<<e.what();
- exit(99);
- }
-
- if(pdns::isQueryLocalAddressFamilyEnabled(AF_INET)) {
- SyncRes::s_doIPv4=true;
- g_log<<Logger::Warning<<"Enabling IPv4 transport for outgoing queries"<<endl;
- }
- else {
- g_log<<Logger::Warning<<"NOT using IPv4 for outgoing queries - add an IPv4 address (like '0.0.0.0') to query-local-address to enable"<<endl;
- }
-
-
- if(pdns::isQueryLocalAddressFamilyEnabled(AF_INET6)) {
- SyncRes::s_doIPv6=true;
- g_log<<Logger::Warning<<"Enabling IPv6 transport for outgoing queries"<<endl;
- }
- else {
- g_log<<Logger::Warning<<"NOT using IPv6 for outgoing queries - add an IPv6 address (like '::') to query-local-address to enable"<<endl;
- }
-
- if (!SyncRes::s_doIPv6 && !SyncRes::s_doIPv4) {
- g_log<<Logger::Error<<"No outgoing addresses configured! Can not continue"<<endl;
- exit(99);
- }
-
- // keep this ABOVE loadRecursorLuaConfig!
- if(::arg()["dnssec"]=="off")
- g_dnssecmode=DNSSECMode::Off;
- else if(::arg()["dnssec"]=="process-no-validate")
- g_dnssecmode=DNSSECMode::ProcessNoValidate;
- else if(::arg()["dnssec"]=="process")
- g_dnssecmode=DNSSECMode::Process;
- else if(::arg()["dnssec"]=="validate")
- g_dnssecmode=DNSSECMode::ValidateAll;
- else if(::arg()["dnssec"]=="log-fail")
- g_dnssecmode=DNSSECMode::ValidateForLog;
- else {
- g_log<<Logger::Error<<"Unknown DNSSEC mode "<<::arg()["dnssec"]<<endl;
- exit(1);
- }
-
- g_signatureInceptionSkew = ::arg().asNum("signature-inception-skew");
- if (g_signatureInceptionSkew < 0) {
- g_log<<Logger::Error<<"A negative value for 'signature-inception-skew' is not allowed"<<endl;
- exit(1);
- }
-
- g_dnssecLogBogus = ::arg().mustDo("dnssec-log-bogus");
- g_maxNSEC3Iterations = ::arg().asNum("nsec3-max-iterations");
-
- g_maxCacheEntries = ::arg().asNum("max-cache-entries");
- g_maxPacketCacheEntries = ::arg().asNum("max-packetcache-entries");
-
- luaConfigDelayedThreads delayedLuaThreads;
- try {
- loadRecursorLuaConfig(::arg()["lua-config-file"], delayedLuaThreads);
- }
- catch (PDNSException &e) {
- g_log<<Logger::Error<<"Cannot load Lua configuration: "<<e.reason<<endl;
- exit(1);
- }
-
- parseACLs();
- initPublicSuffixList(::arg()["public-suffix-list-file"]);
-
- if(!::arg()["dont-query"].empty()) {
- vector<string> ips;
- stringtok(ips, ::arg()["dont-query"], ", ");
- ips.push_back("0.0.0.0");
- ips.push_back("::");
-
- g_log<<Logger::Warning<<"Will not send queries to: ";
- for(vector<string>::const_iterator i = ips.begin(); i!= ips.end(); ++i) {
- SyncRes::addDontQuery(*i);
- if(i!=ips.begin())
- g_log<<Logger::Warning<<", ";
- g_log<<Logger::Warning<<*i;
- }
- g_log<<Logger::Warning<<endl;
- }
-
- /* this needs to be done before parseACLs(), which call broadcastFunction() */
- g_weDistributeQueries = ::arg().mustDo("pdns-distributes-queries");
- if(g_weDistributeQueries) {
- g_log<<Logger::Warning<<"PowerDNS Recursor itself will distribute queries over threads"<<endl;
- }
-
- g_outgoingEDNSBufsize=::arg().asNum("edns-outgoing-bufsize");
-
- if(::arg()["trace"]=="fail") {
- SyncRes::setDefaultLogMode(SyncRes::Store);
- }
- else if(::arg().mustDo("trace")) {
- SyncRes::setDefaultLogMode(SyncRes::Log);
- ::arg().set("quiet")="no";
- g_quiet=false;
- g_dnssecLOG=true;
- }
- string myHostname = getHostname();
- if (myHostname == "UNKNOWN"){
- g_log<<Logger::Warning<<"Unable to get the hostname, NSID and id.server values will be empty"<<endl;
- myHostname = "";
- }
-
- SyncRes::s_minimumTTL = ::arg().asNum("minimum-ttl-override");
- SyncRes::s_minimumECSTTL = ::arg().asNum("ecs-minimum-ttl-override");
-
- SyncRes::s_nopacketcache = ::arg().mustDo("disable-packetcache");
-
- SyncRes::s_maxnegttl=::arg().asNum("max-negative-ttl");
- SyncRes::s_maxbogusttl=::arg().asNum("max-cache-bogus-ttl");
- SyncRes::s_maxcachettl=max(::arg().asNum("max-cache-ttl"), 15);
- SyncRes::s_packetcachettl=::arg().asNum("packetcache-ttl");
- // Cap the packetcache-servfail-ttl to the packetcache-ttl
- uint32_t packetCacheServFailTTL = ::arg().asNum("packetcache-servfail-ttl");
- SyncRes::s_packetcacheservfailttl=(packetCacheServFailTTL > SyncRes::s_packetcachettl) ? SyncRes::s_packetcachettl : packetCacheServFailTTL;
- SyncRes::s_serverdownmaxfails=::arg().asNum("server-down-max-fails");
- SyncRes::s_serverdownthrottletime=::arg().asNum("server-down-throttle-time");
- SyncRes::s_nonresolvingnsmaxfails=::arg().asNum("non-resolving-ns-max-fails");
- SyncRes::s_nonresolvingnsthrottletime=::arg().asNum("non-resolving-ns-throttle-time");
- SyncRes::s_serverID=::arg()["server-id"];
- SyncRes::s_maxqperq=::arg().asNum("max-qperq");
- SyncRes::s_maxnsaddressqperq=::arg().asNum("max-ns-address-qperq");
- SyncRes::s_maxtotusec=1000*::arg().asNum("max-total-msec");
- SyncRes::s_maxdepth=::arg().asNum("max-recursion-depth");
- SyncRes::s_rootNXTrust = ::arg().mustDo( "root-nx-trust");
- SyncRes::s_refresh_ttlperc = ::arg().asNum("refresh-on-ttl-perc");
- RecursorPacketCache::s_refresh_ttlperc = SyncRes::s_refresh_ttlperc;
- SyncRes::s_tcp_fast_open = ::arg().asNum("tcp-fast-open");
- SyncRes::s_tcp_fast_open_connect = ::arg().mustDo("tcp-fast-open-connect");
-
- SyncRes::s_dot_to_port_853 = ::arg().mustDo("dot-to-port-853");
- SyncRes::s_event_trace_enabled = ::arg().asNum("event-trace-enabled");
-
- if (SyncRes::s_tcp_fast_open_connect) {
- checkFastOpenSysctl(true);
- checkTFOconnect();
- }
-
- if(SyncRes::s_serverID.empty()) {
- SyncRes::s_serverID = myHostname;
- }
-
- SyncRes::s_ecsipv4limit = ::arg().asNum("ecs-ipv4-bits");
- SyncRes::s_ecsipv6limit = ::arg().asNum("ecs-ipv6-bits");
- SyncRes::clearECSStats();
- SyncRes::s_ecsipv4cachelimit = ::arg().asNum("ecs-ipv4-cache-bits");
- SyncRes::s_ecsipv6cachelimit = ::arg().asNum("ecs-ipv6-cache-bits");
- SyncRes::s_ecsipv4nevercache = ::arg().mustDo("ecs-ipv4-never-cache");
- SyncRes::s_ecsipv6nevercache = ::arg().mustDo("ecs-ipv6-never-cache");
- SyncRes::s_ecscachelimitttl = ::arg().asNum("ecs-cache-limit-ttl");
-
- SyncRes::s_qnameminimization = ::arg().mustDo("qname-minimization");
-
- if (SyncRes::s_qnameminimization) {
- // With an empty cache, a rev ipv6 query with dnssec enabled takes
- // almost 100 queries. Default maxqperq is 60.
- SyncRes::s_maxqperq = std::max(SyncRes::s_maxqperq, static_cast<unsigned int>(100));
- }
-
- SyncRes::s_hardenNXD = SyncRes::HardenNXD::DNSSEC;
- string value = ::arg()["nothing-below-nxdomain"];
- if (value == "yes") {
- SyncRes::s_hardenNXD = SyncRes::HardenNXD::Yes;
- } else if (value == "no") {
- SyncRes::s_hardenNXD = SyncRes::HardenNXD::No;
- } else if (value != "dnssec") {
- g_log << Logger::Error << "Unknown nothing-below-nxdomain mode: " << value << endl;
- exit(1);
- }
-
- if (!::arg().isEmpty("ecs-scope-zero-address")) {
- ComboAddress scopeZero(::arg()["ecs-scope-zero-address"]);
- SyncRes::setECSScopeZeroAddress(Netmask(scopeZero, scopeZero.isIPv4() ? 32 : 128));
- }
- else {
- Netmask nm;
- bool done = false;
-
- auto addr = pdns::getNonAnyQueryLocalAddress(AF_INET);
- if (addr.sin4.sin_family != 0) {
- nm = Netmask(addr, 32);
- done = true;
- }
- if (!done) {
- addr = pdns::getNonAnyQueryLocalAddress(AF_INET6);
- if (addr.sin4.sin_family != 0) {
- nm = Netmask(addr, 128);
- done = true;
- }
- }
- if (!done) {
- nm = Netmask(ComboAddress("127.0.0.1"), 32);
- }
- SyncRes::setECSScopeZeroAddress(nm);
- }
-
- SyncRes::parseEDNSSubnetAllowlist(::arg()["edns-subnet-whitelist"]);
- SyncRes::parseEDNSSubnetAllowlist(::arg()["edns-subnet-allow-list"]);
- SyncRes::parseEDNSSubnetAddFor(::arg()["ecs-add-for"]);
- g_useIncomingECS = ::arg().mustDo("use-incoming-edns-subnet");
-
- g_XPFAcl.toMasks(::arg()["xpf-allow-from"]);
- g_xpfRRCode = ::arg().asNum("xpf-rr-code");
-
- g_proxyProtocolACL.toMasks(::arg()["proxy-protocol-from"]);
- g_proxyProtocolMaximumSize = ::arg().asNum("proxy-protocol-maximum-size");
-
- if (!::arg()["dns64-prefix"].empty()) {
- try {
- auto dns64Prefix = Netmask(::arg()["dns64-prefix"]);
- if (dns64Prefix.getBits() != 96) {
- g_log << Logger::Error << "Invalid prefix for 'dns64-prefix', the current implementation only supports /96 prefixes: " << ::arg()["dns64-prefix"] << endl;
- exit(1);
- }
- g_dns64Prefix = dns64Prefix.getNetwork();
- g_dns64PrefixReverse = reverseNameFromIP(*g_dns64Prefix);
- /* /96 is 24 nibbles + 2 for "ip6.arpa." */
- while (g_dns64PrefixReverse.countLabels() > 26) {
- g_dns64PrefixReverse.chopOff();
- }
- }
- catch (const NetmaskException& ne) {
- g_log << Logger::Error << "Invalid prefix '" << ::arg()["dns64-prefix"] << "' for 'dns64-prefix': " << ne.reason << endl;
- exit(1);
- }
- }
-
- g_networkTimeoutMsec = ::arg().asNum("network-timeout");
-
- std::tie(g_initialDomainMap, g_initialAllowNotifyFor) = parseZoneConfiguration();
-
- g_latencyStatSize=::arg().asNum("latency-statistic-size");
-
- g_logCommonErrors=::arg().mustDo("log-common-errors");
- g_logRPZChanges = ::arg().mustDo("log-rpz-changes");
-
- g_anyToTcp = ::arg().mustDo("any-to-tcp");
- g_udpTruncationThreshold = ::arg().asNum("udp-truncation-threshold");
-
- g_lowercaseOutgoing = ::arg().mustDo("lowercase-outgoing");
-
- g_paddingFrom.toMasks(::arg()["edns-padding-from"]);
- if (::arg()["edns-padding-mode"] == "always") {
- g_paddingMode = PaddingMode::Always;
- }
- else if (::arg()["edns-padding-mode"] == "padded-queries-only") {
- g_paddingMode = PaddingMode::PaddedQueries;
- }
- else {
- g_log << Logger::Error << "Unknown edns-padding-mode: " << ::arg()["edns-padding-mode"] << endl;
- exit(1);
- }
- g_paddingTag = ::arg().asNum("edns-padding-tag");
-
- g_numDistributorThreads = ::arg().asNum("distributor-threads");
- g_numWorkerThreads = ::arg().asNum("threads");
- if (g_numWorkerThreads < 1) {
- g_log<<Logger::Warning<<"Asked to run with 0 threads, raising to 1 instead"<<endl;
- g_numWorkerThreads = 1;
- }
-
- g_numThreads = g_numDistributorThreads + g_numWorkerThreads;
- g_maxMThreads = ::arg().asNum("max-mthreads");
-
-
- int64_t maxInFlight = ::arg().asNum("max-concurrent-requests-per-tcp-connection");
- if (maxInFlight < 1 || maxInFlight > USHRT_MAX || maxInFlight >= g_maxMThreads) {
- g_log<<Logger::Warning<<"Asked to run with illegal max-concurrent-requests-per-tcp-connection, setting to default (10)"<<endl;
- TCPConnection::s_maxInFlight = 10;
- } else {
- TCPConnection::s_maxInFlight = maxInFlight;
- }
-
- int64_t millis = ::arg().asNum("tcp-out-max-idle-ms");
- TCPOutConnectionManager::s_maxIdleTime = timeval{millis / 1000, (static_cast<suseconds_t>(millis) % 1000) * 1000 };
- TCPOutConnectionManager::s_maxIdlePerAuth = ::arg().asNum("tcp-out-max-idle-per-auth");
- TCPOutConnectionManager::s_maxQueries = ::arg().asNum("tcp-out-max-queries");
- TCPOutConnectionManager::s_maxIdlePerThread = ::arg().asNum("tcp-out-max-idle-per-thread");
-
- g_gettagNeedsEDNSOptions = ::arg().mustDo("gettag-needs-edns-options");
-
- g_statisticsInterval = ::arg().asNum("statistics-interval");
-
- s_addExtendedResolutionDNSErrors = ::arg().mustDo("extended-resolution-errors");
-
- if (::arg().asNum("aggressive-nsec-cache-size") > 0) {
- if (g_dnssecmode == DNSSECMode::ValidateAll || g_dnssecmode == DNSSECMode::ValidateForLog || g_dnssecmode == DNSSECMode::Process) {
- g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(::arg().asNum("aggressive-nsec-cache-size"));
- }
- else {
- g_log<<Logger::Warning<<"Aggressive NSEC/NSEC3 caching is enabled but DNSSEC validation is not set to 'validate', 'log-fail' or 'process', ignoring"<<endl;
- }
- }
-
- {
- SuffixMatchNode dontThrottleNames;
- vector<string> parts;
- stringtok(parts, ::arg()["dont-throttle-names"], " ,");
- for (const auto &p : parts) {
- dontThrottleNames.add(DNSName(p));
- }
- g_dontThrottleNames.setState(std::move(dontThrottleNames));
-
- parts.clear();
- NetmaskGroup dontThrottleNetmasks;
- stringtok(parts, ::arg()["dont-throttle-netmasks"], " ,");
- for (const auto &p : parts) {
- dontThrottleNetmasks.addMask(Netmask(p));
- }
- g_dontThrottleNetmasks.setState(std::move(dontThrottleNetmasks));
- }
-
- {
- SuffixMatchNode xdnssecNames;
- vector<string> parts;
- stringtok(parts, ::arg()["x-dnssec-names"], " ,");
- for (const auto &p : parts) {
- xdnssecNames.add(DNSName(p));
- }
- g_xdnssec.setState(std::move(xdnssecNames));
- }
-
- {
- SuffixMatchNode dotauthNames;
- vector<string> parts;
- stringtok(parts, ::arg()["dot-to-auth-names"], " ,");
-#ifndef HAVE_DNS_OVER_TLS
- if (parts.size()) {
- g_log << Logger::Error << "dot-to-auth-names setting contains names, but Recursor was built without DNS over TLS support. Setting will be ignored."<<endl;
- }
-#endif
- for (const auto &p : parts) {
- dotauthNames.add(DNSName(p));
- }
- g_DoTToAuthNames.setState(std::move(dotauthNames));
- }
-
- {
- CarbonConfig config;
- stringtok(config.servers, arg()["carbon-server"], ", ");
- config.hostname = arg()["carbon-ourname"];
- config.instance_name = arg()["carbon-instance"];
- config.namespace_name = arg()["carbon-namespace"];
- g_carbonConfig.setState(std::move(config));
- }
-
- s_balancingFactor = ::arg().asDouble("distribution-load-factor");
- if (s_balancingFactor != 0.0 && s_balancingFactor < 1.0) {
- s_balancingFactor = 0.0;
- g_log<<Logger::Warning<<"Asked to run with a distribution-load-factor below 1.0, disabling it instead"<<endl;
- }
-
-#ifdef SO_REUSEPORT
- g_reusePort = ::arg().mustDo("reuseport");
-#endif
-
- s_threadInfos.resize(g_numDistributorThreads + g_numWorkerThreads + /* handler */ 1);
-
- if (g_reusePort) {
- if (g_weDistributeQueries) {
- /* first thread is the handler, then distributors */
- for (unsigned int threadId = 1; threadId <= g_numDistributorThreads; threadId++) {
- auto& deferredAdds = s_threadInfos.at(threadId).deferredAdds;
- auto& tcpSockets = s_threadInfos.at(threadId).tcpSockets;
- makeUDPServerSockets(deferredAdds);
- makeTCPServerSockets(deferredAdds, tcpSockets);
- }
- }
- else {
- /* first thread is the handler, there is no distributor here and workers are accepting queries */
- for (unsigned int threadId = 1; threadId <= g_numWorkerThreads; threadId++) {
- auto& deferredAdds = s_threadInfos.at(threadId).deferredAdds;
- auto& tcpSockets = s_threadInfos.at(threadId).tcpSockets;
- makeUDPServerSockets(deferredAdds);
- makeTCPServerSockets(deferredAdds, tcpSockets);
- }
- }
- }
- else {
- std::set<int> tcpSockets;
- /* we don't have reuseport so we can only open one socket per
- listening addr:port and everyone will listen on it */
- makeUDPServerSockets(g_deferredAdds);
- makeTCPServerSockets(g_deferredAdds, tcpSockets);
-
- /* every listener (so distributor if g_weDistributeQueries, workers otherwise)
- needs to listen to the shared sockets */
- if (g_weDistributeQueries) {
- /* first thread is the handler, then distributors */
- for (unsigned int threadId = 1; threadId <= g_numDistributorThreads; threadId++) {
- s_threadInfos.at(threadId).tcpSockets = tcpSockets;
- }
- }
- else {
- /* first thread is the handler, there is no distributor here and workers are accepting queries */
- for (unsigned int threadId = 1; threadId <= g_numWorkerThreads; threadId++) {
- s_threadInfos.at(threadId).tcpSockets = tcpSockets;
- }
- }
- }
-
-#ifdef NOD_ENABLED
- // Setup newly observed domain globals
- setupNODGlobal();
-#endif /* NOD_ENABLED */
-
- int forks;
- for(forks = 0; forks < ::arg().asNum("processes") - 1; ++forks) {
- if(!fork()) // we are child
- break;
- }
-
- if(::arg().mustDo("daemon")) {
- g_log<<Logger::Warning<<"Calling daemonize, going to background"<<endl;
- g_log.toConsole(Logger::Critical);
- daemonize();
- }
- if(Utility::getpid() == 1) {
- /* We are running as pid 1, register sigterm and sigint handler
-
- The Linux kernel will handle SIGTERM and SIGINT for all processes, except PID 1.
- It assumes that the processes running as pid 1 is an "init" like system.
- For years, this was a safe assumption, but containers change that: in
- most (all?) container implementations, the application itself is running
- as pid 1. This means that sending signals to those applications, will not
- be handled by default. Results might be "your container not responding
- when asking it to stop", or "ctrl-c not working even when the app is
- running in the foreground inside a container".
-
- So TL;DR: If we're running pid 1 (container), we should handle SIGTERM and SIGINT ourselves */
-
- signal(SIGTERM,termIntHandler);
- signal(SIGINT,termIntHandler);
- }
-
- signal(SIGUSR1,usr1Handler);
- signal(SIGUSR2,usr2Handler);
- signal(SIGPIPE,SIG_IGN);
-
- checkOrFixFDS();
-
-#ifdef HAVE_LIBSODIUM
- if (sodium_init() == -1) {
- g_log<<Logger::Error<<"Unable to initialize sodium crypto library"<<endl;
- exit(99);
- }
-#endif
-
- openssl_thread_setup();
- openssl_seed();
- /* setup rng before chroot */
- dns_random_init();
-
- if(::arg()["server-id"].empty()) {
- ::arg().set("server-id") = myHostname;
- }
-
- int newgid=0;
- if(!::arg()["setgid"].empty())
- newgid = strToGID(::arg()["setgid"]);
- int newuid=0;
- if(!::arg()["setuid"].empty())
- newuid = strToUID(::arg()["setuid"]);
-
- Utility::dropGroupPrivs(newuid, newgid);
-
- if (!::arg()["chroot"].empty()) {
-#ifdef HAVE_SYSTEMD
- char *ns;
- ns = getenv("NOTIFY_SOCKET");
- if (ns != nullptr) {
- g_log<<Logger::Error<<"Unable to chroot when running from systemd. Please disable chroot= or set the 'Type' for this service to 'simple'"<<endl;
- exit(1);
- }
-#endif
- if (chroot(::arg()["chroot"].c_str())<0 || chdir("/") < 0) {
- int err = errno;
- g_log<<Logger::Error<<"Unable to chroot to '"+::arg()["chroot"]+"': "<<strerror (err)<<", exiting"<<endl;
- exit(1);
- }
- else
- g_log<<Logger::Info<<"Chrooted to '"<<::arg()["chroot"]<<"'"<<endl;
- }
-
- checkSocketDir();
-
- s_pidfname=::arg()["socket-dir"]+"/"+s_programname+".pid";
- if(!s_pidfname.empty())
- unlink(s_pidfname.c_str()); // remove possible old pid file
- writePid();
-
- makeControlChannelSocket( ::arg().asNum("processes") > 1 ? forks : -1);
-
- Utility::dropUserPrivs(newuid);
- try {
- /* we might still have capabilities remaining, for example if we have been started as root
- without --setuid (please don't do that) or as an unprivileged user with ambient capabilities
- like CAP_NET_BIND_SERVICE.
- */
- dropCapabilities();
- }
- catch(const std::exception& e) {
- g_log<<Logger::Warning<<e.what()<<endl;
- }
-
- startLuaConfigDelayedThreads(delayedLuaThreads, g_luaconfs.getCopy().generation);
- delayedLuaThreads.rpzPrimaryThreads.clear(); // no longer needed
- delayedLuaThreads.ztcConfigs.clear(); // no longer needed
-
- makeThreadPipes();
-
- g_tcpTimeout=::arg().asNum("client-tcp-timeout");
- g_maxTCPPerClient=::arg().asNum("max-tcp-per-client");
- g_tcpMaxQueriesPerConn=::arg().asNum("max-tcp-queries-per-connection");
- s_maxUDPQueriesPerRound=::arg().asNum("max-udp-queries-per-round");
-
- g_useKernelTimestamp = ::arg().mustDo("protobuf-use-kernel-timestamp");
-
- disableStats(StatComponent::API, ::arg()["stats-api-blacklist"]);
- disableStats(StatComponent::Carbon, ::arg()["stats-carbon-blacklist"]);
- disableStats(StatComponent::RecControl, ::arg()["stats-rec-control-blacklist"]);
- disableStats(StatComponent::SNMP, ::arg()["stats-snmp-blacklist"]);
-
- disableStats(StatComponent::API, ::arg()["stats-api-disabled-list"]);
- disableStats(StatComponent::Carbon, ::arg()["stats-carbon-disabled-list"]);
- disableStats(StatComponent::RecControl, ::arg()["stats-rec-control-disabled-list"]);
- disableStats(StatComponent::SNMP, ::arg()["stats-snmp-disabled-list"]);
-
- if (::arg().mustDo("snmp-agent")) {
- string setting = ::arg()["snmp-daemon-socket"];
- if (setting.empty()) {
- setting = ::arg()["snmp-master-socket"];
- }
- g_snmpAgent = std::make_shared<RecursorSNMPAgent>("recursor", setting);
- g_snmpAgent->run();
- }
-
- int port = ::arg().asNum("udp-source-port-min");
- if(port < 1024 || port > 65535){
- g_log<<Logger::Error<<"Unable to launch, udp-source-port-min is not a valid port number"<<endl;
- exit(99); // this isn't going to fix itself either
- }
- s_minUdpSourcePort = port;
- port = ::arg().asNum("udp-source-port-max");
- if(port < 1024 || port > 65535 || port < s_minUdpSourcePort){
- g_log<<Logger::Error<<"Unable to launch, udp-source-port-max is not a valid port number or is smaller than udp-source-port-min"<<endl;
- exit(99); // this isn't going to fix itself either
- }
- s_maxUdpSourcePort = port;
- std::vector<string> parts {};
- stringtok(parts, ::arg()["udp-source-port-avoid"], ", ");
- for (const auto &part : parts)
- {
- port = std::stoi(part);
- if(port < 1024 || port > 65535){
- g_log<<Logger::Error<<"Unable to launch, udp-source-port-avoid contains an invalid port number: "<<part<<endl;
- exit(99); // this isn't going to fix itself either
- }
- s_avoidUdpSourcePorts.insert(port);
- }
-
- unsigned int currentThreadId = 1;
- const auto cpusMap = parseCPUMap();
-
- if(g_numThreads == 1) {
- g_log<<Logger::Warning<<"Operating unthreaded"<<endl;
-#ifdef HAVE_SYSTEMD
- sd_notify(0, "READY=1");
-#endif
-
- /* This thread handles the web server, carbon, statistics and the control channel */
- auto& handlerInfos = s_threadInfos.at(0);
- handlerInfos.isHandler = true;
- handlerInfos.thread = std::thread(recursorThread, 0, "web+stat");
-
- setCPUMap(cpusMap, currentThreadId, pthread_self());
-
- auto& infos = s_threadInfos.at(currentThreadId);
- infos.isListener = true;
- infos.isWorker = true;
- recursorThread(currentThreadId++, "worker");
-
- handlerInfos.thread.join();
- if (handlerInfos.exitCode != 0) {
- ret = handlerInfos.exitCode;
- }
- }
- else {
-
-
- if (g_weDistributeQueries) {
- for(unsigned int n=0; n < g_numDistributorThreads; ++n) {
- auto& infos = s_threadInfos.at(currentThreadId + n);
- infos.isListener = true;
- }
- }
- for(unsigned int n=0; n < g_numWorkerThreads; ++n) {
- auto& infos = s_threadInfos.at(currentThreadId + (g_weDistributeQueries ? g_numDistributorThreads : 0) + n);
- infos.isListener = !g_weDistributeQueries;
- infos.isWorker = true;
- }
-
- if (g_weDistributeQueries) {
- g_log<<Logger::Warning<<"Launching "<< g_numDistributorThreads <<" distributor threads"<<endl;
- for(unsigned int n=0; n < g_numDistributorThreads; ++n) {
- auto& infos = s_threadInfos.at(currentThreadId);
- infos.thread = std::thread(recursorThread, currentThreadId++, "distr");
- setCPUMap(cpusMap, currentThreadId, infos.thread.native_handle());
- }
- }
-
- g_log<<Logger::Warning<<"Launching "<< g_numWorkerThreads <<" worker threads"<<endl;
-
- for(unsigned int n=0; n < g_numWorkerThreads; ++n) {
- auto& infos = s_threadInfos.at(currentThreadId);
- infos.thread = std::thread(recursorThread, currentThreadId++, "worker");
- setCPUMap(cpusMap, currentThreadId, infos.thread.native_handle());
- }
-
-#ifdef HAVE_SYSTEMD
- sd_notify(0, "READY=1");
-#endif
-
- /* This thread handles the web server, carbon, statistics and the control channel */
- auto& infos = s_threadInfos.at(0);
- infos.isHandler = true;
- infos.thread = std::thread(recursorThread, 0, "web+stat");
-
- for (auto & ti : s_threadInfos) {
- ti.thread.join();
- if (ti.exitCode != 0) {
- ret = ti.exitCode;
- }
- }
- }
-
- return ret;
-}
-
-static void* recursorThread(unsigned int n, const string& threadName)
-try
-{
- t_id=n;
- auto& threadInfo = s_threadInfos.at(t_id);
-
- static string threadPrefix = "pdns-r/";
- setThreadName(threadPrefix + threadName);
-
- SyncRes tmp(g_now); // make sure it allocates tsstorage before we do anything, like primeHints or so..
- SyncRes::setDomainMap(g_initialDomainMap);
- t_allowFrom = g_initialAllowFrom;
- t_allowNotifyFrom = g_initialAllowNotifyFrom;
- t_allowNotifyFor = g_initialAllowNotifyFor;
- t_udpclientsocks = std::make_unique<UDPClientSocks>();
- t_tcpClientCounts = std::make_unique<tcpClientCounts_t>();
-
- if (threadInfo.isHandler) {
- if (!primeHints()) {
- threadInfo.exitCode = EXIT_FAILURE;
- RecursorControlChannel::stop = 1;
- g_log<<Logger::Critical<<"Priming cache failed, stopping"<<endl;
- return nullptr;
- }
- g_log<<Logger::Warning<<"Done priming cache with root hints"<<endl;
- }
-
- t_packetCache = std::make_unique<RecursorPacketCache>();
-
-#ifdef NOD_ENABLED
- if (threadInfo.isWorker)
- setupNODThread();
-#endif /* NOD_ENABLED */
-
- /* the listener threads handle TCP queries */
- if(threadInfo.isWorker || threadInfo.isListener) {
- try {
- if(!::arg()["lua-dns-script"].empty()) {
- t_pdl = std::make_shared<RecursorLua4>();
- t_pdl->loadFile(::arg()["lua-dns-script"]);
- g_log<<Logger::Warning<<"Loaded 'lua' script from '"<<::arg()["lua-dns-script"]<<"'"<<endl;
- }
- }
- catch(std::exception &e) {
- g_log<<Logger::Error<<"Failed to load 'lua' script from '"<<::arg()["lua-dns-script"]<<"': "<<e.what()<<endl;
- _exit(99);
- }
- }
-
- unsigned int ringsize=::arg().asNum("stats-ringbuffer-entries") / g_numWorkerThreads;
- if(ringsize) {
- t_remotes = std::make_unique<addrringbuf_t>();
- if(g_weDistributeQueries)
- t_remotes->set_capacity(::arg().asNum("stats-ringbuffer-entries") / g_numDistributorThreads);
- else
- t_remotes->set_capacity(ringsize);
- t_servfailremotes = std::make_unique<addrringbuf_t>();
- t_servfailremotes->set_capacity(ringsize);
- t_bogusremotes = std::make_unique<addrringbuf_t>();
- t_bogusremotes->set_capacity(ringsize);
- t_largeanswerremotes = std::make_unique<addrringbuf_t>();
- t_largeanswerremotes->set_capacity(ringsize);
- t_timeouts = std::make_unique<addrringbuf_t>();
- t_timeouts->set_capacity(ringsize);
-
- t_queryring = std::make_unique<boost::circular_buffer<pair<DNSName, uint16_t>>>();
- t_queryring->set_capacity(ringsize);
- t_servfailqueryring = std::make_unique<boost::circular_buffer<pair<DNSName, uint16_t>>>();
- t_servfailqueryring->set_capacity(ringsize);
- t_bogusqueryring = std::make_unique<boost::circular_buffer<pair<DNSName, uint16_t>>>();
- t_bogusqueryring->set_capacity(ringsize);
- }
- MT = std::make_unique<MT_t>(::arg().asNum("stack-size"));
- threadInfo.mt = MT.get();
-
- /* start protobuf export threads if needed */
- auto luaconfsLocal = g_luaconfs.getLocal();
- checkProtobufExport(luaconfsLocal);
- checkOutgoingProtobufExport(luaconfsLocal);
-#ifdef HAVE_FSTRM
- checkFrameStreamExport(luaconfsLocal);
-#endif
-
- PacketID pident;
-
- t_fdm=getMultiplexer();
-
- RecursorWebServer *rws = nullptr;
-
- t_fdm->addReadFD(threadInfo.pipes.readToThread, handlePipeRequest);
-
- if(threadInfo.isHandler) {
- if(::arg().mustDo("webserver")) {
- g_log<<Logger::Warning << "Enabling web server" << endl;
- try {
- rws = new RecursorWebServer(t_fdm);
- }
- catch (const PDNSException &e) {
- g_log<<Logger::Error<<"Unable to start the internal web server: "<<e.reason<<endl;
- _exit(99);
- }
- }
- g_log<<Logger::Info<<"Enabled '"<< t_fdm->getName() << "' multiplexer"<<endl;
- }
- else {
- t_fdm->addReadFD(threadInfo.pipes.readQueriesToThread, handlePipeRequest);
-
- if (threadInfo.isListener) {
- if (g_reusePort) {
- /* then every listener has its own FDs */
- for(const auto& deferred : threadInfo.deferredAdds) {
- t_fdm->addReadFD(deferred.first, deferred.second);
- }
- }
- else {
- /* otherwise all listeners are listening on the same ones */
- for(const auto& deferred : g_deferredAdds) {
- t_fdm->addReadFD(deferred.first, deferred.second);
- }
- }
- }
- }
-
- registerAllStats();
-
- if(threadInfo.isHandler) {
- t_fdm->addReadFD(s_rcc.d_fd, handleRCC); // control channel
- }
-
- unsigned int maxTcpClients=::arg().asNum("max-tcp-clients");
-
- bool listenOnTCP(true);
-
- time_t last_stat = 0;
- time_t last_carbon=0, last_lua_maintenance=0;
- time_t carbonInterval=::arg().asNum("carbon-interval");
- time_t luaMaintenanceInterval=::arg().asNum("lua-maintenance-interval");
- counter.store(0); // used to periodically execute certain tasks
-
- while (!RecursorControlChannel::stop) {
- while(MT->schedule(&g_now)); // MTasker letting the mthreads do their thing
-
- // Use primes, it avoid not being scheduled in cases where the counter has a regular pattern.
- // We want to call handler thread often, it gets scheduled about 2 times per second
- if ((threadInfo.isHandler && counter % 11 == 0) || counter % 499 == 0) {
- MT->makeThread(houseKeeping, 0);
- }
-
- if(!(counter%55)) {
- typedef vector<pair<int, FDMultiplexer::funcparam_t> > expired_t;
- expired_t expired=t_fdm->getTimeouts(g_now);
-
- for(expired_t::iterator i=expired.begin() ; i != expired.end(); ++i) {
- shared_ptr<TCPConnection> conn=boost::any_cast<shared_ptr<TCPConnection> >(i->second);
- if(g_logCommonErrors)
- g_log<<Logger::Warning<<"Timeout from remote TCP client "<< conn->d_remote.toStringWithPort() <<endl;
- t_fdm->removeReadFD(i->first);
- }
- }
-
- counter++;
-
- if(threadInfo.isHandler) {
- if(statsWanted || (g_statisticsInterval > 0 && (g_now.tv_sec - last_stat) >= g_statisticsInterval)) {
- doStats();
- last_stat = g_now.tv_sec;
- }
-
- Utility::gettimeofday(&g_now, nullptr);
-
- if((g_now.tv_sec - last_carbon) >= carbonInterval) {
- MT->makeThread(doCarbonDump, 0);
- last_carbon = g_now.tv_sec;
- }
- }
- if (t_pdl != nullptr) {
- // lua-dns-script directive is present, call the maintenance callback if needed
- /* remember that the listener threads handle TCP queries */
- if (threadInfo.isWorker || threadInfo.isListener) {
- // Only on threads processing queries
- if(g_now.tv_sec - last_lua_maintenance >= luaMaintenanceInterval) {
- t_pdl->maintenance();
- last_lua_maintenance = g_now.tv_sec;
- }
- }
- }
-
- t_fdm->run(&g_now);
- // 'run' updates g_now for us
-
- if(threadInfo.isListener) {
- if(listenOnTCP) {
- if(TCPConnection::getCurrentConnections() > maxTcpClients) { // shutdown, too many connections
- for(const auto fd : threadInfo.tcpSockets) {
- t_fdm->removeReadFD(fd);
- }
- listenOnTCP=false;
- }
- }
- else {
- if(TCPConnection::getCurrentConnections() <= maxTcpClients) { // reenable
- for(const auto fd : threadInfo.tcpSockets) {
- t_fdm->addReadFD(fd, handleNewTCPQuestion);
- }
- listenOnTCP=true;
- }
- }
- }
- }
- delete rws;
- delete t_fdm;
- return 0;
-}
-catch(PDNSException &ae) {
- g_log<<Logger::Error<<"Exception: "<<ae.reason<<endl;
- return 0;
-}
-catch(std::exception &e) {
- g_log<<Logger::Error<<"STL Exception: "<<e.what()<<endl;
- return 0;
-}
-catch(...) {
- g_log<<Logger::Error<<"any other exception in main: "<<endl;
- return 0;
-}
-
-//static std::string s_timestampFormat = "%m-%dT%H:%M:%S";
-static std::string s_timestampFormat = "%s";
-
-static const char* toTimestampStringMilli(const struct timeval& tv, char *buf, size_t sz)
-{
- struct tm tm;
- size_t len = strftime(buf, sz, s_timestampFormat.c_str(), localtime_r(&tv.tv_sec, &tm));
- if (len == 0) {
- len = snprintf(buf, sz, "%lld", static_cast<long long>(tv.tv_sec));
- }
-
- snprintf(buf + len, sz - len, ".%03ld", static_cast<long>(tv.tv_usec) / 1000);
- return buf;
-}
-
-static void loggerBackend(const Logging::Entry& entry) {
- static thread_local std::stringstream buf;
-
- buf.str("");
- buf << "msg=" << std::quoted(entry.message);
- if (entry.error) {
- buf << " oserror=" << std::quoted(entry.error.get());
- }
-
- if (entry.name) {
- buf << " subsystem=" << std::quoted(entry.name.get());
- }
- buf << " level=" << entry.level;
- if (entry.d_priority) {
- buf << " prio=" << static_cast<int>(entry.d_priority);
- }
- char timebuf[64];
- buf << " ts=" << std::quoted(toTimestampStringMilli(entry.d_timestamp, timebuf, sizeof(timebuf)));
- for (auto const& v: entry.values) {
- buf << " ";
- buf << v.first << "=" << std::quoted(v.second);
- }
- Logger::Urgency u = entry.d_priority ? Logger::Urgency(entry.d_priority) : Logger::Info;
- g_log << u << buf.str() << endl;
-}
-
-
-int main(int argc, char **argv)
-{
- g_argc = argc;
- g_argv = argv;
- g_stats.startupTime=time(0);
- Utility::srandom();
- versionSetProduct(ProductRecursor);
- reportBasicTypes();
- reportOtherTypes();
-
- int ret = EXIT_SUCCESS;
-
- try {
-#if HAVE_FIBER_SANITIZER
- // Asan needs more stack
- ::arg().set("stack-size","stack size per mthread")="400000";
-#else
- ::arg().set("stack-size","stack size per mthread")="200000";
-#endif
- ::arg().set("soa-minimum-ttl","Don't change")="0";
- ::arg().set("no-shuffle","Don't change")="off";
- ::arg().set("local-port","port to listen on")="53";
- ::arg().set("local-address","IP addresses to listen on, separated by spaces or commas. Also accepts ports.")="127.0.0.1";
- ::arg().setSwitch("non-local-bind", "Enable binding to non-local addresses by using FREEBIND / BINDANY socket options")="no";
- ::arg().set("trace","if we should output heaps of logging. set to 'fail' to only log failing domains")="off";
- ::arg().set("dnssec", "DNSSEC mode: off/process-no-validate/process (default)/log-fail/validate")="process";
- ::arg().set("dnssec-log-bogus", "Log DNSSEC bogus validations")="no";
- ::arg().set("signature-inception-skew", "Allow the signature inception to be off by this number of seconds")="60";
- ::arg().set("daemon","Operate as a daemon")="no";
- ::arg().setSwitch("write-pid","Write a PID file")="yes";
- ::arg().set("loglevel","Amount of logging. Higher is more. Do not set below 3")="6";
- ::arg().set("disable-syslog","Disable logging to syslog, useful when running inside a supervisor that logs stdout")="no";
- ::arg().set("log-timestamp","Print timestamps in log lines, useful to disable when running with a tool that timestamps stdout already")="yes";
- ::arg().set("log-common-errors","If we should log rather common errors")="no";
- ::arg().set("chroot","switch to chroot jail")="";
- ::arg().set("setgid","If set, change group id to this gid for more security"
-#ifdef HAVE_SYSTEMD
-#define SYSTEMD_SETID_MSG ". When running inside systemd, use the User and Group settings in the unit-file!"
- SYSTEMD_SETID_MSG
-#endif
- )="";
- ::arg().set("setuid","If set, change user id to this uid for more security"
-#ifdef HAVE_SYSTEMD
- SYSTEMD_SETID_MSG
-#endif
- )="";
- ::arg().set("network-timeout", "Wait this number of milliseconds for network i/o")="1500";
- ::arg().set("threads", "Launch this number of threads")="2";
- ::arg().set("distributor-threads", "Launch this number of distributor threads, distributing queries to other threads")="0";
- ::arg().set("processes", "Launch this number of processes (EXPERIMENTAL, DO NOT CHANGE)")="1"; // if we un-experimental this, need to fix openssl rand seeding for multiple PIDs!
- ::arg().set("config-name","Name of this virtual configuration - will rename the binary image")="";
- ::arg().set("api-config-dir", "Directory where REST API stores config and zones") = "";
- ::arg().set("api-key", "Static pre-shared authentication key for access to the REST API") = "";
- ::arg().setSwitch("webserver", "Start a webserver (for REST API)") = "no";
- ::arg().set("webserver-address", "IP Address of webserver to listen on") = "127.0.0.1";
- ::arg().set("webserver-port", "Port of webserver to listen on") = "8082";
- ::arg().set("webserver-password", "Password required for accessing the webserver") = "";
- ::arg().set("webserver-allow-from","Webserver access is only allowed from these subnets")="127.0.0.1,::1";
- ::arg().set("webserver-loglevel", "Amount of logging in the webserver (none, normal, detailed)") = "normal";
- ::arg().setSwitch("webserver-hash-plaintext-credentials","Whether to hash passwords and api keys supplied in plaintext, to prevent keeping the plaintext version in memory at runtime")="no";
- ::arg().set("carbon-ourname", "If set, overrides our reported hostname for carbon stats")="";
- ::arg().set("carbon-server", "If set, send metrics in carbon (graphite) format to this server IP address")="";
- ::arg().set("carbon-interval", "Number of seconds between carbon (graphite) updates")="30";
- ::arg().set("carbon-namespace", "If set overwrites the first part of the carbon string")="pdns";
- ::arg().set("carbon-instance", "If set overwrites the instance name default")="recursor";
-
- ::arg().set("statistics-interval", "Number of seconds between printing of recursor statistics, 0 to disable")="1800";
- ::arg().set("quiet","Suppress logging of questions and answers")="";
- ::arg().set("logging-facility","Facility to log messages as. 0 corresponds to local0")="";
- ::arg().set("config-dir","Location of configuration directory (recursor.conf)")=SYSCONFDIR;
- ::arg().set("socket-owner","Owner of socket")="";
- ::arg().set("socket-group","Group of socket")="";
- ::arg().set("socket-mode", "Permissions for socket")="";
-
- ::arg().set("socket-dir",string("Where the controlsocket will live, ")+LOCALSTATEDIR+"/pdns-recursor when unset and not chrooted"
-#ifdef HAVE_SYSTEMD
- + ". Set to the RUNTIME_DIRECTORY environment variable when that variable has a value (e.g. under systemd).")="";
- auto runtimeDir = getenv("RUNTIME_DIRECTORY");
- if (runtimeDir != nullptr) {
- ::arg().set("socket-dir") = runtimeDir;
- }
-#else
- )="";
-#endif
- ::arg().set("query-local-address","Source IP address for sending queries")="0.0.0.0";
- ::arg().set("client-tcp-timeout","Timeout in seconds when talking to TCP clients")="2";
- ::arg().set("max-mthreads", "Maximum number of simultaneous Mtasker threads")="2048";
- ::arg().set("max-tcp-clients","Maximum number of simultaneous TCP clients")="128";
- ::arg().set("max-concurrent-requests-per-tcp-connection", "Maximum number of requests handled concurrently per TCP connection") = "10";
- ::arg().set("server-down-max-fails","Maximum number of consecutive timeouts (and unreachables) to mark a server as down ( 0 => disabled )")="64";
- ::arg().set("server-down-throttle-time","Number of seconds to throttle all queries to a server after being marked as down")="60";
- ::arg().set("dont-throttle-names", "Do not throttle nameservers with this name or suffix")="";
- ::arg().set("dont-throttle-netmasks", "Do not throttle nameservers with this IP netmask")="";
- ::arg().set("non-resolving-ns-max-fails", "Number of failed address resolves of a nameserver to start throttling it, 0 is disabled")="5";
- ::arg().set("non-resolving-ns-throttle-time", "Number of seconds to throttle a nameserver with a name failing to resolve")="60";
-
- ::arg().set("hint-file", "If set, load root hints from this file")="";
- ::arg().set("max-cache-entries", "If set, maximum number of entries in the main cache")="1000000";
- ::arg().set("max-negative-ttl", "maximum number of seconds to keep a negative cached entry in memory")="3600";
- ::arg().set("max-cache-bogus-ttl", "maximum number of seconds to keep a Bogus (positive or negative) cached entry in memory")="3600";
- ::arg().set("max-cache-ttl", "maximum number of seconds to keep a cached entry in memory")="86400";
- ::arg().set("packetcache-ttl", "maximum number of seconds to keep a cached entry in packetcache")="3600";
- ::arg().set("max-packetcache-entries", "maximum number of entries to keep in the packetcache")="500000";
- ::arg().set("packetcache-servfail-ttl", "maximum number of seconds to keep a cached servfail entry in packetcache")="60";
- ::arg().set("server-id", "Returned when queried for 'id.server' TXT or NSID, defaults to hostname, set custom or 'disabled'")="";
- ::arg().set("stats-ringbuffer-entries", "maximum number of packets to store statistics for")="10000";
- ::arg().set("version-string", "string reported on version.pdns or version.bind")=fullVersionString();
- ::arg().set("allow-from", "If set, only allow these comma separated netmasks to recurse")=LOCAL_NETS;
- ::arg().set("allow-from-file", "If set, load allowed netmasks from this file")="";
- ::arg().set("allow-notify-for", "If set, NOTIFY requests for these zones will be allowed")="";
- ::arg().set("allow-notify-for-file", "If set, load NOTIFY-allowed zones from this file")="";
- ::arg().set("allow-notify-from", "If set, NOTIFY requests from these comma separated netmasks will be allowed")="";
- ::arg().set("allow-notify-from-file", "If set, load NOTIFY-allowed netmasks from this file")="";
- ::arg().set("entropy-source", "If set, read entropy from this file")="/dev/urandom";
- ::arg().set("dont-query", "If set, do not query these netmasks for DNS data")=DONT_QUERY;
- ::arg().set("max-tcp-per-client", "If set, maximum number of TCP sessions per client (IP address)")="0";
- ::arg().set("max-tcp-queries-per-connection", "If set, maximum number of TCP queries in a TCP connection")="0";
- ::arg().set("spoof-nearmiss-max", "If non-zero, assume spoofing after this many near misses")="1";
- ::arg().set("single-socket", "If set, only use a single socket for outgoing queries")="off";
- ::arg().set("auth-zones", "Zones for which we have authoritative data, comma separated domain=file pairs ")="";
- ::arg().set("lua-config-file", "More powerful configuration options")="";
- ::arg().setSwitch("allow-trust-anchor-query", "Allow queries for trustanchor.server CH TXT and negativetrustanchor.server CH TXT")="no";
-
- ::arg().set("forward-zones", "Zones for which we forward queries, comma separated domain=ip pairs")="";
- ::arg().set("forward-zones-recurse", "Zones for which we forward queries with recursion bit, comma separated domain=ip pairs")="";
- ::arg().set("forward-zones-file", "File with (+)domain=ip pairs for forwarding")="";
- ::arg().set("export-etc-hosts", "If we should serve up contents from /etc/hosts")="off";
- ::arg().set("export-etc-hosts-search-suffix", "Also serve up the contents of /etc/hosts with this suffix")="";
- ::arg().set("etc-hosts-file", "Path to 'hosts' file")="/etc/hosts";
- ::arg().set("serve-rfc1918", "If we should be authoritative for RFC 1918 private IP space")="yes";
- ::arg().set("lua-dns-script", "Filename containing an optional 'lua' script that will be used to modify dns answers")="";
- ::arg().set("lua-maintenance-interval", "Number of seconds between calls to the lua user defined maintenance() function")="1";
- ::arg().set("latency-statistic-size","Number of latency values to calculate the qa-latency average")="10000";
- ::arg().setSwitch( "disable-packetcache", "Disable packetcache" )= "no";
- ::arg().set("ecs-ipv4-bits", "Number of bits of IPv4 address to pass for EDNS Client Subnet")="24";
- ::arg().set("ecs-ipv4-cache-bits", "Maximum number of bits of IPv4 mask to cache ECS response")="24";
- ::arg().set("ecs-ipv6-bits", "Number of bits of IPv6 address to pass for EDNS Client Subnet")="56";
- ::arg().set("ecs-ipv6-cache-bits", "Maximum number of bits of IPv6 mask to cache ECS response")="56";
- ::arg().setSwitch("ecs-ipv4-never-cache", "If we should never cache IPv4 ECS responses")="no";
- ::arg().setSwitch("ecs-ipv6-never-cache", "If we should never cache IPv6 ECS responses")="no";
- ::arg().set("ecs-minimum-ttl-override", "The minimum TTL for records in ECS-specific answers")="1";
- ::arg().set("ecs-cache-limit-ttl", "Minimum TTL to cache ECS response")="0";
- ::arg().set("edns-subnet-whitelist", "List of netmasks and domains that we should enable EDNS subnet for (deprecated)")="";
- ::arg().set("edns-subnet-allow-list", "List of netmasks and domains that we should enable EDNS subnet for")="";
- ::arg().set("ecs-add-for", "List of client netmasks for which EDNS Client Subnet will be added")="0.0.0.0/0, ::/0, " LOCAL_NETS_INVERSE;
- ::arg().set("ecs-scope-zero-address", "Address to send to allow-listed authoritative servers for incoming queries with ECS prefix-length source of 0")="";
- ::arg().setSwitch( "use-incoming-edns-subnet", "Pass along received EDNS Client Subnet information")="no";
- ::arg().setSwitch( "pdns-distributes-queries", "If PowerDNS itself should distribute queries over threads")="yes";
- ::arg().setSwitch( "root-nx-trust", "If set, believe that an NXDOMAIN from the root means the TLD does not exist")="yes";
- ::arg().setSwitch( "any-to-tcp","Answer ANY queries with tc=1, shunting to TCP" )="no";
- ::arg().setSwitch( "lowercase-outgoing","Force outgoing questions to lowercase")="no";
- ::arg().setSwitch("gettag-needs-edns-options", "If EDNS Options should be extracted before calling the gettag() hook")="no";
- ::arg().set("udp-truncation-threshold", "Maximum UDP response size before we truncate")="1232";
- ::arg().set("edns-outgoing-bufsize", "Outgoing EDNS buffer size")="1232";
- ::arg().set("minimum-ttl-override", "The minimum TTL")="1";
- ::arg().set("max-qperq", "Maximum outgoing queries per query")="60";
- ::arg().set("max-ns-address-qperq", "Maximum outgoing NS address queries per query")="10";
- ::arg().set("max-total-msec", "Maximum total wall-clock time per query in milliseconds, 0 for unlimited")="7000";
- ::arg().set("max-recursion-depth", "Maximum number of internal recursion calls per query, 0 for unlimited")="40";
- ::arg().set("max-udp-queries-per-round", "Maximum number of UDP queries processed per recvmsg() round, before returning back to normal processing")="10000";
- ::arg().set("protobuf-use-kernel-timestamp", "Compute the latency of queries in protobuf messages by using the timestamp set by the kernel when the query was received (when available)")="";
- ::arg().set("distribution-pipe-buffer-size", "Size in bytes of the internal buffer of the pipe used by the distributor to pass incoming queries to a worker thread")="0";
-
- ::arg().set("include-dir","Include *.conf files from this directory")="";
- ::arg().set("security-poll-suffix","Domain name from which to query security update notifications")="secpoll.powerdns.com.";
-
- ::arg().setSwitch("reuseport","Enable SO_REUSEPORT allowing multiple recursors processes to listen to 1 address")="no";
-
- ::arg().setSwitch("snmp-agent", "If set, register as an SNMP agent")="no";
- ::arg().set("snmp-master-socket", "If set and snmp-agent is set, the socket to use to register to the SNMP daemon (deprecated)")="";
- ::arg().set("snmp-daemon-socket", "If set and snmp-agent is set, the socket to use to register to the SNMP daemon")="";
-
- std::string defaultAPIDisabledStats = "cache-bytes, packetcache-bytes, special-memory-usage";
- for (size_t idx = 0; idx < 32; idx++) {
- defaultAPIDisabledStats += ", ecs-v4-response-bits-" + std::to_string(idx + 1);
- }
- for (size_t idx = 0; idx < 128; idx++) {
- defaultAPIDisabledStats += ", ecs-v6-response-bits-" + std::to_string(idx + 1);
- }
- std::string defaultDisabledStats = defaultAPIDisabledStats + ", cumul-clientanswers, cumul-authanswers, policy-hits";
-
- ::arg().set("stats-api-blacklist", "List of statistics that are disabled when retrieving the complete list of statistics via the API (deprecated)")=defaultAPIDisabledStats;
- ::arg().set("stats-carbon-blacklist", "List of statistics that are prevented from being exported via Carbon (deprecated)")=defaultDisabledStats;
- ::arg().set("stats-rec-control-blacklist", "List of statistics that are prevented from being exported via rec_control get-all (deprecated)")=defaultDisabledStats;
- ::arg().set("stats-snmp-blacklist", "List of statistics that are prevented from being exported via SNMP (deprecated)")=defaultDisabledStats;
-
- ::arg().set("stats-api-disabled-list", "List of statistics that are disabled when retrieving the complete list of statistics via the API")=defaultAPIDisabledStats;
- ::arg().set("stats-carbon-disabled-list", "List of statistics that are prevented from being exported via Carbon")=defaultDisabledStats;
- ::arg().set("stats-rec-control-disabled-list", "List of statistics that are prevented from being exported via rec_control get-all")=defaultDisabledStats;
- ::arg().set("stats-snmp-disabled-list", "List of statistics that are prevented from being exported via SNMP")=defaultDisabledStats;
-
- ::arg().set("tcp-fast-open", "Enable TCP Fast Open support on the listening sockets, using the supplied numerical value as the queue size")="0";
- ::arg().set("tcp-fast-open-connect", "Enable TCP Fast Open support on outgoing sockets")="no";
- ::arg().set("nsec3-max-iterations", "Maximum number of iterations allowed for an NSEC3 record")="150";
-
- ::arg().set("cpu-map", "Thread to CPU mapping, space separated thread-id=cpu1,cpu2..cpuN pairs")="";
-
- ::arg().setSwitch("log-rpz-changes", "Log additions and removals to RPZ zones at Info level")="no";
-
- ::arg().set("xpf-allow-from","XPF information is only processed from these subnets")="";
- ::arg().set("xpf-rr-code","XPF option code to use")="0";
-
- ::arg().set("proxy-protocol-from", "A Proxy Protocol header is only allowed from these subnets")="";
- ::arg().set("proxy-protocol-maximum-size", "The maximum size of a proxy protocol payload, including the TLV values")="512";
-
- ::arg().set("dns64-prefix", "DNS64 prefix")="";
-
- ::arg().set("udp-source-port-min", "Minimum UDP port to bind on")="1024";
- ::arg().set("udp-source-port-max", "Maximum UDP port to bind on")="65535";
- ::arg().set("udp-source-port-avoid", "List of comma separated UDP port number to avoid")="11211";
- ::arg().set("rng", "Specify random number generator to use. Valid values are auto,sodium,openssl,getrandom,arc4random,urandom.")="auto";
- ::arg().set("public-suffix-list-file", "Path to the Public Suffix List file, if any")="";
- ::arg().set("distribution-load-factor", "The load factor used when PowerDNS is distributing queries to worker threads")="0.0";
-
- ::arg().setSwitch("qname-minimization", "Use Query Name Minimization")="yes";
- ::arg().setSwitch("nothing-below-nxdomain", "When an NXDOMAIN exists in cache for a name with fewer labels than the qname, send NXDOMAIN without doing a lookup (see RFC 8020)")="dnssec";
- ::arg().set("max-generate-steps", "Maximum number of $GENERATE steps when loading a zone from a file")="0";
- ::arg().set("max-include-depth", "Maximum nested $INCLUDE depth when loading a zone from a file")="20";
- ::arg().set("record-cache-shards", "Number of shards in the record cache")="1024";
- ::arg().set("refresh-on-ttl-perc", "If a record is requested from the cache and only this % of original TTL remains, refetch") = "0";
-
- ::arg().set("x-dnssec-names", "Collect DNSSEC statistics for names or suffixes in this list in separate x-dnssec counters")="";
-
-#ifdef NOD_ENABLED
- ::arg().set("new-domain-tracking", "Track newly observed domains (i.e. never seen before).")="no";
- ::arg().set("new-domain-log", "Log newly observed domains.")="yes";
- ::arg().set("new-domain-lookup", "Perform a DNS lookup newly observed domains as a subdomain of the configured domain")="";
- ::arg().set("new-domain-history-dir", "Persist new domain tracking data here to persist between restarts")=string(NODCACHEDIR)+"/nod";
- ::arg().set("new-domain-whitelist", "List of domains (and implicitly all subdomains) which will never be considered a new domain (deprecated)")="";
- ::arg().set("new-domain-ignore-list", "List of domains (and implicitly all subdomains) which will never be considered a new domain")="";
- ::arg().set("new-domain-db-size", "Size of the DB used to track new domains in terms of number of cells. Defaults to 67108864")="67108864";
- ::arg().set("new-domain-pb-tag", "If protobuf is configured, the tag to use for messages containing newly observed domains. Defaults to 'pdns-nod'")="pdns-nod";
- ::arg().set("unique-response-tracking", "Track unique responses (tuple of query name, type and RR).")="no";
- ::arg().set("unique-response-log", "Log unique responses")="yes";
- ::arg().set("unique-response-history-dir", "Persist unique response tracking data here to persist between restarts")=string(NODCACHEDIR)+"/udr";
- ::arg().set("unique-response-db-size", "Size of the DB used to track unique responses in terms of number of cells. Defaults to 67108864")="67108864";
- ::arg().set("unique-response-pb-tag", "If protobuf is configured, the tag to use for messages containing unique DNS responses. Defaults to 'pdns-udr'")="pdns-udr";
-#endif /* NOD_ENABLED */
-
- ::arg().setSwitch("extended-resolution-errors", "If set, send an EDNS Extended Error extension on resolution failures, like DNSSEC validation errors")="no";
-
- ::arg().set("aggressive-nsec-cache-size", "The number of records to cache in the aggressive cache. If set to a value greater than 0, and DNSSEC processing or validation is enabled, the recursor will cache NSEC and NSEC3 records to generate negative answers, as defined in rfc8198")="100000";
-
- ::arg().set("edns-padding-from", "List of netmasks (proxy IP in case of XPF or proxy-protocol presence, client IP otherwise) for which EDNS padding will be enabled in responses, provided that 'edns-padding-mode' applies")="";
- ::arg().set("edns-padding-mode", "Whether to add EDNS padding to all responses ('always') or only to responses for queries containing the EDNS padding option ('padded-queries-only', the default). In both modes, padding will only be added to responses for queries coming from `edns-padding-from`_ sources")="padded-queries-only";
- ::arg().set("edns-padding-tag", "Packetcache tag associated to responses sent with EDNS padding, to prevent sending these to clients for which padding is not enabled.")="7830";
-
- ::arg().setSwitch("dot-to-port-853", "Force DoT connection to target port 853 if DoT compiled in")="yes";
- ::arg().set("dot-to-auth-names", "Use DoT to authoritative servers with these names or suffixes")="";
- ::arg().set("event-trace-enabled", "If set, event traces are collected and send out via protobuf logging (1), logfile (2) or both(3)")="0";
-
- ::arg().set("tcp-out-max-idle-ms", "Time TCP/DoT connections are left idle in milliseconds or 0 if no limit") = "10000";
- ::arg().set("tcp-out-max-idle-per-auth", "Maximum number of idle TCP/DoT connections to a specific IP per thread, 0 means do not keep idle connections open") = "10";
- ::arg().set("tcp-out-max-queries", "Maximum total number of queries per TCP/DoT connection, 0 means no limit") = "0";
- ::arg().set("tcp-out-max-idle-per-thread", "Maximum number of idle TCP/DoT connections per thread") = "100";
- ::arg().setSwitch("structured-logging", "Prefer structured logging") = "yes";
-
- ::arg().setCmd("help","Provide a helpful message");
- ::arg().setCmd("version","Print version string");
- ::arg().setCmd("config","Output blank configuration");
- ::arg().setDefaults();
- g_log.toConsole(Logger::Info);
- ::arg().laxParse(argc,argv); // do a lax parse
-
- if(::arg().mustDo("version")) {
- showProductVersion();
- showBuildConfiguration();
- exit(0);
- }
-
- string configname=::arg()["config-dir"]+"/recursor.conf";
- if(::arg()["config-name"]!="") {
- configname=::arg()["config-dir"]+"/recursor-"+::arg()["config-name"]+".conf";
- s_programname+="-"+::arg()["config-name"];
- }
- cleanSlashes(configname);
-
- if(!::arg().getCommands().empty()) {
- cerr<<"Fatal: non-option";
- if (::arg().getCommands().size() > 1) {
- cerr<<"s";
- }
- cerr<<" (";
- bool first = true;
- for (const auto& c : ::arg().getCommands()) {
- if (!first) {
- cerr<<", ";
- }
- first = false;
- cerr<<c;
- }
- cerr<<") on the command line, perhaps a '--setting=123' statement missed the '='?"<<endl;
- exit(99);
- }
-
- if(::arg().mustDo("config")) {
- cout<<::arg().configstring(false, true);
- exit(0);
- }
-
- g_slog = Logging::Logger::create(loggerBackend);
- auto startupLog = g_slog->withName("startup");
-
- if(!::arg().file(configname.c_str())) {
- SLOG(g_log<<Logger::Warning<<"Unable to parse configuration file '"<<configname<<"'"<<endl,
- startupLog->error("No such file", "Unable to parse configuration file", "config_file", Logging::Loggable(configname)));
- }
-
- ::arg().parse(argc,argv);
-
- if( !::arg()["chroot"].empty() && !::arg()["api-config-dir"].empty() ) {
- SLOG(g_log<<Logger::Error<<"Using chroot and enabling the API is not possible"<<endl,
- startupLog->info("Cannot use chroot and enable the API at the same time"));
- exit(EXIT_FAILURE);
- }
-
- if (::arg()["socket-dir"].empty()) {
- if (::arg()["chroot"].empty())
- ::arg().set("socket-dir") = std::string(LOCALSTATEDIR) + "/pdns-recursor";
- else
- ::arg().set("socket-dir") = "/";
- }
-
- if(::arg().asNum("threads")==1) {
- if (::arg().mustDo("pdns-distributes-queries")) {
- SLOG(g_log<<Logger::Warning<<"Asked to run with pdns-distributes-queries set but no distributor threads, raising to 1"<<endl,
- startupLog->v(1)->info("Only one thread, no need to distribute queries ourselves"));
- ::arg().set("pdns-distributes-queries")="no";
- }
- }
-
- if(::arg().mustDo("pdns-distributes-queries") && ::arg().asNum("distributor-threads") <= 0) {
- SLOG(g_log<<Logger::Warning<<"Asked to run with pdns-distributes-queries set but no distributor threads, raising to 1"<<endl,
- startupLog->v(1)->info("Asked to run with pdns-distributes-queries set but no distributor threads, raising to 1"));
- ::arg().set("distributor-threads")="1";
- }
-
- if (!::arg().mustDo("pdns-distributes-queries")) {
- ::arg().set("distributor-threads")="0";
- }
-
- if(::arg().mustDo("help")) {
- cout<<"syntax:"<<endl<<endl;
- cout<<::arg().helpstring(::arg()["help"])<<endl;
- exit(0);
- }
- g_recCache = std::make_unique<MemRecursorCache>(::arg().asNum("record-cache-shards"));
- g_negCache = std::make_unique<NegCache>(::arg().asNum("record-cache-shards"));
-
- g_quiet=::arg().mustDo("quiet");
- Logger::Urgency logUrgency = (Logger::Urgency)::arg().asNum("loglevel");
-
- if (logUrgency < Logger::Error)
- logUrgency = Logger::Error;
- if(!g_quiet && logUrgency < Logger::Info) { // Logger::Info=6, Logger::Debug=7
- logUrgency = Logger::Info; // if you do --quiet=no, you need Info to also see the query log
- }
- g_log.setLoglevel(logUrgency);
- g_log.toConsole(logUrgency);
-
- ret = serviceMain(argc, argv);
- }
- catch(PDNSException &ae) {
- g_log<<Logger::Error<<"Exception: "<<ae.reason<<endl;
- ret=EXIT_FAILURE;
- }
- catch(std::exception &e) {
- g_log<<Logger::Error<<"STL Exception: "<<e.what()<<endl;
- ret=EXIT_FAILURE;
- }
- catch(...) {
- g_log<<Logger::Error<<"any other exception in main: "<<endl;
- ret=EXIT_FAILURE;
- }
-
- return ret;
-}
-
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include <sys/stat.h>
#include "rec-main.hh"
+#include "aggressive_nsec.hh"
+#include "capabilities.hh"
+#include "arguments.hh"
+#include "dns_random.hh"
+#include "rec_channel.hh"
+#include "rec-tcpout.hh"
+#include "version.hh"
+#include "query-local-address.hh"
+#include "validate-recursor.hh"
+#include "pubsuffix.hh"
+#include "opensslsigners.hh"
+#include "threadname.hh"
+#include "ws-recursor.hh"
+#include "rec-taskqueue.hh"
+#include "secpoll-recursor.hh"
+
+#ifdef NOD_ENABLED
+#include "nod.hh"
+#include "logging.hh"
+#endif /* NOD_ENABLED */
+
+#ifdef HAVE_LIBSODIUM
+#include <sodium.h>
+#endif
+
+#ifdef HAVE_SYSTEMD
+#include <systemd/sd-daemon.h>
+#endif
+
+static thread_local uint64_t t_protobufServersGeneration;
+static thread_local uint64_t t_outgoingProtobufServersGeneration;
+
+#ifdef HAVE_FSTRM
+thread_local std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>> t_frameStreamServers{nullptr};
+thread_local uint64_t t_frameStreamServersGeneration;
+#endif /* HAVE_FSTRM */
+
+string s_programname="pdns_recursor";
+string s_pidfname;
+RecursorControlChannel s_rcc; // only active in the handler thread
+
+#ifdef NOD_ENABLED
+bool g_nodEnabled;
+DNSName g_nodLookupDomain;
+bool g_nodLog;
+SuffixMatchNode g_nodDomainWL;
+std::string g_nod_pbtag;
+bool g_udrEnabled;
+bool g_udrLog;
+std::string g_udr_pbtag;
+thread_local std::shared_ptr<nod::NODDB> t_nodDBp;
+thread_local std::shared_ptr<nod::UniqueResponseDB> t_udrDBp;
+#endif /* NOD_ENABLED */
+
+std::atomic<bool> statsWanted;
+unsigned int g_numWorkerThreads;
+uint32_t g_disthashseed;
+bool g_weDistributeQueries; // if true, 1 or more threads listen on the incoming query sockets and distribute them to workers
+bool g_useIncomingECS;
+uint16_t g_xpfRRCode{0};
+NetmaskGroup g_proxyProtocolACL;
+boost::optional<ComboAddress> g_dns64Prefix{boost::none};
+DNSName g_dns64PrefixReverse;
+std::shared_ptr<SyncRes::domainmap_t> g_initialDomainMap; // new threads needs this to be setup
+std::shared_ptr<NetmaskGroup> g_initialAllowFrom; // new thread needs to be setup with this
+std::shared_ptr<NetmaskGroup> g_initialAllowNotifyFrom; // new threads need this to be setup
+std::shared_ptr<notifyset_t> g_initialAllowNotifyFor; // new threads need this to be setup
+bool g_logRPZChanges{false};
+unsigned int g_numDistributorThreads;
+unsigned int g_numThreads;
+static time_t g_statisticsInterval;
+bool s_addExtendedResolutionDNSErrors;
+static std::atomic<uint32_t> counter;
+int g_argc;
+char** g_argv;
+
+/* without reuseport, all listeners share the same sockets */
+deferredAdd_t g_deferredAdds;
+
+/* first we have the handler thread, t_id == 0 (some other
+ helper threads like SNMP might have t_id == 0 as well)
+ then the distributor threads if any
+ and finally the workers */
+std::vector<RecThreadInfo> s_threadInfos;
+
+ArgvMap &arg()
+{
+ static ArgvMap theArg;
+ return theArg;
+}
+
+static FDMultiplexer* getMultiplexer()
+{
+ FDMultiplexer* ret;
+ for(const auto& i : FDMultiplexer::getMultiplexerMap()) {
+ try {
+ ret=i.second();
+ return ret;
+ }
+ catch(FDMultiplexerException &fe) {
+ g_log<<Logger::Error<<"Non-fatal error initializing possible multiplexer ("<<fe.what()<<"), falling back"<<endl;
+ }
+ catch(...) {
+ g_log<<Logger::Error<<"Non-fatal error initializing possible multiplexer"<<endl;
+ }
+ }
+ g_log<<Logger::Error<<"No working multiplexer found!"<<endl;
+ _exit(1);
+}
+
+static std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>> startProtobufServers(const ProtobufExportConfig& config)
+{
+ auto result = std::make_shared<std::vector<std::unique_ptr<RemoteLogger>>>();
+
+ for (const auto& server : config.servers) {
+ try {
+ auto logger = make_unique<RemoteLogger>(server, config.timeout, 100*config.maxQueuedEntries, config.reconnectWaitTime, config.asyncConnect);
+ logger->setLogQueries(config.logQueries);
+ logger->setLogResponses(config.logResponses);
+ result->emplace_back(std::move(logger));
+ }
+ catch(const std::exception& e) {
+ g_log<<Logger::Error<<"Error while starting protobuf logger to '"<<server<<": "<<e.what()<<endl;
+ }
+ catch(const PDNSException& e) {
+ g_log<<Logger::Error<<"Error while starting protobuf logger to '"<<server<<": "<<e.reason<<endl;
+ }
+ }
+
+ return result;
+}
+
+bool checkProtobufExport(LocalStateHolder<LuaConfigItems>& luaconfsLocal)
+{
+ if (!luaconfsLocal->protobufExportConfig.enabled) {
+ if (t_protobufServers) {
+ for (auto& server : *t_protobufServers) {
+ server->stop();
+ }
+ t_protobufServers.reset();
+ }
+
+ return false;
+ }
+
+ /* if the server was not running, or if it was running according to a
+ previous configuration */
+ if (!t_protobufServers ||
+ t_protobufServersGeneration < luaconfsLocal->generation) {
+
+ if (t_protobufServers) {
+ for (auto& server : *t_protobufServers) {
+ server->stop();
+ }
+ }
+ t_protobufServers.reset();
+
+ t_protobufServers = startProtobufServers(luaconfsLocal->protobufExportConfig);
+ t_protobufServersGeneration = luaconfsLocal->generation;
+ }
+
+ return true;
+}
+
+bool checkOutgoingProtobufExport(LocalStateHolder<LuaConfigItems>& luaconfsLocal)
+{
+ if (!luaconfsLocal->outgoingProtobufExportConfig.enabled) {
+ if (t_outgoingProtobufServers) {
+ for (auto& server : *t_outgoingProtobufServers) {
+ server->stop();
+ }
+ }
+ t_outgoingProtobufServers.reset();
+
+ return false;
+ }
+
+ /* if the server was not running, or if it was running according to a
+ previous configuration */
+ if (!t_outgoingProtobufServers ||
+ t_outgoingProtobufServersGeneration < luaconfsLocal->generation) {
+
+ if (t_outgoingProtobufServers) {
+ for (auto& server : *t_outgoingProtobufServers) {
+ server->stop();
+ }
+ }
+ t_outgoingProtobufServers.reset();
+
+ t_outgoingProtobufServers = startProtobufServers(luaconfsLocal->outgoingProtobufExportConfig);
+ t_outgoingProtobufServersGeneration = luaconfsLocal->generation;
+ }
+
+ return true;
+}
+
+void protobufLogQuery(LocalStateHolder<LuaConfigItems>& luaconfsLocal, const boost::uuids::uuid& uniqueId, const ComboAddress& remote, const ComboAddress& local, const Netmask& ednssubnet, bool tcp, uint16_t id, size_t len, const DNSName& qname, uint16_t qtype, uint16_t qclass, const std::unordered_set<std::string>& policyTags, const std::string& requestorId, const std::string& deviceId, const std::string& deviceName, const std::map<std::string, RecursorLua4::MetaValue>& meta)
+{
+ if (!t_protobufServers) {
+ return;
+ }
+
+ Netmask requestorNM(remote, remote.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
+ ComboAddress requestor = requestorNM.getMaskedNetwork();
+ requestor.setPort(remote.getPort());
+
+ pdns::ProtoZero::RecMessage m{128, std::string::size_type(policyTags.empty() ? 0 : 64)}; // It's a guess
+ m.setType(pdns::ProtoZero::Message::MessageType::DNSQueryType);
+ m.setRequest(uniqueId, requestor, local, qname, qtype, qclass, id, tcp ? pdns::ProtoZero::Message::TransportProtocol::TCP : pdns::ProtoZero::Message::TransportProtocol::UDP, len);
+ m.setServerIdentity(SyncRes::s_serverID);
+ m.setEDNSSubnet(ednssubnet, ednssubnet.isIPv4() ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
+ m.setRequestorId(requestorId);
+ m.setDeviceId(deviceId);
+ m.setDeviceName(deviceName);
+
+ if (!policyTags.empty()) {
+ m.addPolicyTags(policyTags);
+ }
+ for (const auto& mit : meta) {
+ m.setMeta(mit.first, mit.second.stringVal, mit.second.intVal);
+ }
+
+ std::string msg(m.finishAndMoveBuf());
+ for (auto& server : *t_protobufServers) {
+ server->queueData(msg);
+ }
+}
+
+void protobufLogResponse(pdns::ProtoZero::RecMessage& message)
+{
+ if (!t_protobufServers) {
+ return;
+ }
+
+ std::string msg(message.finishAndMoveBuf());
+ for (auto& server : *t_protobufServers) {
+ server->queueData(msg);
+ }
+}
+
+void protobufLogResponse(const struct dnsheader* dh, LocalStateHolder<LuaConfigItems>& luaconfsLocal,
+ const RecursorPacketCache::OptPBData& pbData, const struct timeval& tv,
+ bool tcp, const ComboAddress& source, const ComboAddress& destination,
+ const EDNSSubnetOpts& ednssubnet,
+ const boost::uuids::uuid& uniqueId, const string& requestorId, const string& deviceId,
+ const string& deviceName, const std::map<std::string, RecursorLua4::MetaValue>& meta,
+ const RecEventTrace& eventTrace)
+{
+ pdns::ProtoZero::RecMessage pbMessage(pbData ? pbData->d_message : "", pbData ? pbData->d_response : "", 64, 10); // The extra bytes we are going to add
+ // Normally we take the immutable string from the cache and append a few values, but if it's not there (can this happen?)
+ // we start with an empty string and append the minimal
+ if (!pbData) {
+ pbMessage.setType(pdns::ProtoZero::Message::MessageType::DNSResponseType);
+ pbMessage.setServerIdentity(SyncRes::s_serverID);
+ }
+
+ // In response part
+ if (g_useKernelTimestamp && tv.tv_sec) {
+ pbMessage.setQueryTime(tv.tv_sec, tv.tv_usec);
+ }
+ else {
+ pbMessage.setQueryTime(g_now.tv_sec, g_now.tv_usec);
+ }
+
+ // In message part
+ Netmask requestorNM(source, source.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
+ ComboAddress requestor = requestorNM.getMaskedNetwork();
+ pbMessage.setMessageIdentity(uniqueId);
+ pbMessage.setFrom(requestor);
+ pbMessage.setTo(destination);
+ pbMessage.setSocketProtocol(tcp ? pdns::ProtoZero::Message::TransportProtocol::TCP : pdns::ProtoZero::Message::TransportProtocol::UDP);
+ pbMessage.setId(dh->id);
+
+ pbMessage.setTime();
+ pbMessage.setEDNSSubnet(ednssubnet.source, ednssubnet.source.isIPv4() ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
+ pbMessage.setRequestorId(requestorId);
+ pbMessage.setDeviceId(deviceId);
+ pbMessage.setDeviceName(deviceName);
+ pbMessage.setFromPort(source.getPort());
+ pbMessage.setToPort(destination.getPort());
+ for (const auto& m : meta) {
+ pbMessage.setMeta(m.first, m.second.stringVal, m.second.intVal);
+ }
+#ifdef NOD_ENABLED
+ if (g_nodEnabled) {
+ pbMessage.setNewlyObservedDomain(false);
+ }
+#endif
+ if (eventTrace.enabled() && SyncRes::s_event_trace_enabled & SyncRes::event_trace_to_pb) {
+ pbMessage.addEvents(eventTrace);
+ }
+ protobufLogResponse(pbMessage);
+}
+
+#ifdef HAVE_FSTRM
+
+static std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>> startFrameStreamServers(const FrameStreamExportConfig& config)
+{
+ auto result = std::make_shared<std::vector<std::unique_ptr<FrameStreamLogger>>>();
+
+ for (const auto& server : config.servers) {
+ try {
+ std::unordered_map<string,unsigned> options;
+ options["bufferHint"] = config.bufferHint;
+ options["flushTimeout"] = config.flushTimeout;
+ options["inputQueueSize"] = config.inputQueueSize;
+ options["outputQueueSize"] = config.outputQueueSize;
+ options["queueNotifyThreshold"] = config.queueNotifyThreshold;
+ options["reopenInterval"] = config.reopenInterval;
+ FrameStreamLogger *fsl = nullptr;
+ try {
+ ComboAddress address(server);
+ fsl = new FrameStreamLogger(address.sin4.sin_family, address.toStringWithPort(), true, options);
+ }
+ catch (const PDNSException& e) {
+ fsl = new FrameStreamLogger(AF_UNIX, server, true, options);
+ }
+ fsl->setLogQueries(config.logQueries);
+ fsl->setLogResponses(config.logResponses);
+ result->emplace_back(fsl);
+ }
+ catch(const std::exception& e) {
+ g_log<<Logger::Error<<"Error while starting dnstap framestream logger to '"<<server<<": "<<e.what()<<endl;
+ }
+ catch(const PDNSException& e) {
+ g_log<<Logger::Error<<"Error while starting dnstap framestream logger to '"<<server<<": "<<e.reason<<endl;
+ }
+ }
+
+ return result;
+}
+
+bool checkFrameStreamExport(LocalStateHolder<LuaConfigItems>& luaconfsLocal)
+{
+ if (!luaconfsLocal->frameStreamExportConfig.enabled) {
+ if (t_frameStreamServers) {
+ // dt's take care of cleanup
+ t_frameStreamServers.reset();
+ }
+
+ return false;
+ }
+
+ /* if the server was not running, or if it was running according to a
+ previous configuration */
+ if (!t_frameStreamServers ||
+ t_frameStreamServersGeneration < luaconfsLocal->generation) {
+
+ if (t_frameStreamServers) {
+ // dt's take care of cleanup
+ t_frameStreamServers.reset();
+ }
+
+ t_frameStreamServers = startFrameStreamServers(luaconfsLocal->frameStreamExportConfig);
+ t_frameStreamServersGeneration = luaconfsLocal->generation;
+ }
+
+ return true;
+}
+#endif /* HAVE_FSTRM */
+
+static void makeControlChannelSocket(int processNum=-1)
+{
+ string sockname=::arg()["socket-dir"]+"/"+s_programname;
+ if(processNum >= 0)
+ sockname += "."+std::to_string(processNum);
+ sockname+=".controlsocket";
+ s_rcc.listen(sockname);
+
+ int sockowner = -1;
+ int sockgroup = -1;
+
+ if (!::arg().isEmpty("socket-group"))
+ sockgroup=::arg().asGid("socket-group");
+ if (!::arg().isEmpty("socket-owner"))
+ sockowner=::arg().asUid("socket-owner");
+
+ if (sockgroup > -1 || sockowner > -1) {
+ if(chown(sockname.c_str(), sockowner, sockgroup) < 0) {
+ unixDie("Failed to chown control socket");
+ }
+ }
+
+ // do mode change if socket-mode is given
+ if(!::arg().isEmpty("socket-mode")) {
+ mode_t sockmode=::arg().asMode("socket-mode");
+ if(chmod(sockname.c_str(), sockmode) < 0) {
+ unixDie("Failed to chmod control socket");
+ }
+ }
+}
+
+static void writePid(void)
+{
+ if(!::arg().mustDo("write-pid"))
+ return;
+ ofstream of(s_pidfname.c_str(), std::ios_base::app);
+ if(of)
+ of<< Utility::getpid() <<endl;
+ else {
+ int err = errno;
+ g_log << Logger::Error << "Writing pid for " << Utility::getpid() << " to " << s_pidfname << " failed: "
+ << stringerror(err) << endl;
+ }
+}
+
+static std::map<unsigned int, std::set<int> > parseCPUMap()
+{
+ std::map<unsigned int, std::set<int> > result;
+
+ const std::string value = ::arg()["cpu-map"];
+
+ if (!value.empty() && !isSettingThreadCPUAffinitySupported()) {
+ g_log<<Logger::Warning<<"CPU mapping requested but not supported, skipping"<<endl;
+ return result;
+ }
+
+ std::vector<std::string> parts;
+
+ stringtok(parts, value, " \t");
+
+ for(const auto& part : parts) {
+ if (part.find('=') == string::npos)
+ continue;
+
+ try {
+ auto headers = splitField(part, '=');
+ boost::trim(headers.first);
+ boost::trim(headers.second);
+
+ unsigned int threadId = pdns_stou(headers.first);
+ std::vector<std::string> cpus;
+
+ stringtok(cpus, headers.second, ",");
+
+ for(const auto& cpu : cpus) {
+ int cpuId = std::stoi(cpu);
+
+ result[threadId].insert(cpuId);
+ }
+ }
+ catch(const std::exception& e) {
+ g_log<<Logger::Error<<"Error parsing cpu-map entry '"<<part<<"': "<<e.what()<<endl;
+ }
+ }
+
+ return result;
+}
+
+static void setCPUMap(const std::map<unsigned int, std::set<int> >& cpusMap, unsigned int n, pthread_t tid)
+{
+ const auto& cpuMapping = cpusMap.find(n);
+ if (cpuMapping != cpusMap.cend()) {
+ int rc = mapThreadToCPUList(tid, cpuMapping->second);
+ if (rc == 0) {
+ g_log<<Logger::Info<<"CPU affinity for worker "<<n<<" has been set to CPU map:";
+ for (const auto cpu : cpuMapping->second) {
+ g_log<<Logger::Info<<" "<<cpu;
+ }
+ g_log<<Logger::Info<<endl;
+ }
+ else {
+ g_log<<Logger::Warning<<"Error setting CPU affinity for worker "<<n<<" to CPU map:";
+ for (const auto cpu : cpuMapping->second) {
+ g_log<<Logger::Info<<" "<<cpu;
+ }
+ g_log<<Logger::Info<<strerror(rc)<<endl;
+ }
+ }
+}
+
+static void checkSocketDir(void)
+{
+ struct stat st;
+ string dir(::arg()["socket-dir"]);
+ string msg;
+
+ if (stat(dir.c_str(), &st) == -1) {
+ msg = "it does not exist or cannot access";
+ }
+ else if (!S_ISDIR(st.st_mode)) {
+ msg = "it is not a directory";
+ }
+ else if (access(dir.c_str(), R_OK | W_OK | X_OK) != 0) {
+ msg = "cannot read, write or search";
+ } else {
+ return;
+ }
+ g_log << Logger::Error << "Problem with socket directory " << dir << ": " << msg << "; see https://docs.powerdns.com/recursor/upgrade.html#x-to-4-3-0" << endl;
+ _exit(1);
+}
+
+#ifdef NOD_ENABLED
+static void setupNODThread()
+{
+ if (g_nodEnabled) {
+ uint32_t num_cells = ::arg().asNum("new-domain-db-size");
+ t_nodDBp = std::make_shared<nod::NODDB>(num_cells);
+ try {
+ t_nodDBp->setCacheDir(::arg()["new-domain-history-dir"]);
+ }
+ catch (const PDNSException& e) {
+ g_log<<Logger::Error<<"new-domain-history-dir (" << ::arg()["new-domain-history-dir"] << ") is not readable or does not exist"<<endl;
+ _exit(1);
+ }
+ if (!t_nodDBp->init()) {
+ g_log<<Logger::Error<<"Could not initialize domain tracking"<<endl;
+ _exit(1);
+ }
+ std::thread t(nod::NODDB::startHousekeepingThread, t_nodDBp, std::this_thread::get_id());
+ t.detach();
+ g_nod_pbtag = ::arg()["new-domain-pb-tag"];
+ }
+ if (g_udrEnabled) {
+ uint32_t num_cells = ::arg().asNum("unique-response-db-size");
+ t_udrDBp = std::make_shared<nod::UniqueResponseDB>(num_cells);
+ try {
+ t_udrDBp->setCacheDir(::arg()["unique-response-history-dir"]);
+ }
+ catch (const PDNSException& e) {
+ g_log<<Logger::Error<<"unique-response-history-dir (" << ::arg()["unique-response-history-dir"] << ") is not readable or does not exist"<<endl;
+ _exit(1);
+ }
+ if (!t_udrDBp->init()) {
+ g_log<<Logger::Error<<"Could not initialize unique response tracking"<<endl;
+ _exit(1);
+ }
+ std::thread t(nod::UniqueResponseDB::startHousekeepingThread, t_udrDBp, std::this_thread::get_id());
+ t.detach();
+ g_udr_pbtag = ::arg()["unique-response-pb-tag"];
+ }
+}
+
+static void parseNODIgnorelist(const std::string& wlist)
+{
+ vector<string> parts;
+ stringtok(parts, wlist, ",; ");
+ for(const auto& a : parts) {
+ g_nodDomainWL.add(DNSName(a));
+ }
+}
+
+static void setupNODGlobal()
+{
+ // Setup NOD subsystem
+ g_nodEnabled = ::arg().mustDo("new-domain-tracking");
+ g_nodLookupDomain = DNSName(::arg()["new-domain-lookup"]);
+ g_nodLog = ::arg().mustDo("new-domain-log");
+ parseNODIgnorelist(::arg()["new-domain-whitelist"]);
+ parseNODIgnorelist(::arg()["new-domain-ignore-list"]);
+
+ // Setup Unique DNS Response subsystem
+ g_udrEnabled = ::arg().mustDo("unique-response-tracking");
+ g_udrLog = ::arg().mustDo("unique-response-log");
+}
+#endif /* NOD_ENABLED */
+
+static void daemonize(void)
+{
+ if(fork())
+ exit(0); // bye bye
+
+ setsid();
+
+ int i=open("/dev/null",O_RDWR); /* open stdin */
+ if(i < 0)
+ g_log<<Logger::Critical<<"Unable to open /dev/null: "<<stringerror()<<endl;
+ else {
+ dup2(i,0); /* stdin */
+ dup2(i,1); /* stderr */
+ dup2(i,2); /* stderr */
+ close(i);
+ }
+}
+
+static void termIntHandler(int)
+{
+ doExit();
+}
+
+static void usr1Handler(int)
+{
+ statsWanted=true;
+}
+
+static void usr2Handler(int)
+{
+ g_quiet= !g_quiet;
+ SyncRes::setDefaultLogMode(g_quiet ? SyncRes::LogNone : SyncRes::Log);
+ ::arg().set("quiet")=g_quiet ? "" : "no";
+}
+
+static void checkLinuxIPv6Limits()
+{
+#ifdef __linux__
+ string line;
+ if(readFileIfThere("/proc/sys/net/ipv6/route/max_size", &line)) {
+ int lim=std::stoi(line);
+ if(lim < 16384) {
+ g_log<<Logger::Error<<"If using IPv6, please raise sysctl net.ipv6.route.max_size, currently set to "<<lim<<" which is < 16384"<<endl;
+ }
+ }
+#endif
+}
+static void checkOrFixFDS()
+{
+ unsigned int availFDs=getFilenumLimit();
+ unsigned int wantFDs = g_maxMThreads * g_numWorkerThreads +25; // even healthier margin then before
+ wantFDs += g_numWorkerThreads * TCPOutConnectionManager::s_maxIdlePerThread;
+
+ if(wantFDs > availFDs) {
+ unsigned int hardlimit= getFilenumLimit(true);
+ if(hardlimit >= wantFDs) {
+ setFilenumLimit(wantFDs);
+ g_log<<Logger::Warning<<"Raised soft limit on number of filedescriptors to "<<wantFDs<<" to match max-mthreads and threads settings"<<endl;
+ }
+ else {
+ int newval = (hardlimit - 25 - TCPOutConnectionManager::s_maxIdlePerThread) / g_numWorkerThreads;
+ g_log<<Logger::Warning<<"Insufficient number of filedescriptors available for max-mthreads*threads setting! ("<<hardlimit<<" < "<<wantFDs<<"), reducing max-mthreads to "<<newval<<endl;
+ g_maxMThreads = newval;
+ setFilenumLimit(hardlimit);
+ }
+ }
+}
+//static std::string s_timestampFormat = "%m-%dT%H:%M:%S";
+static std::string s_timestampFormat = "%s";
+
+static const char* toTimestampStringMilli(const struct timeval& tv, char *buf, size_t sz)
+{
+ struct tm tm;
+ size_t len = strftime(buf, sz, s_timestampFormat.c_str(), localtime_r(&tv.tv_sec, &tm));
+ if (len == 0) {
+ len = snprintf(buf, sz, "%lld", static_cast<long long>(tv.tv_sec));
+ }
+
+ snprintf(buf + len, sz - len, ".%03ld", static_cast<long>(tv.tv_usec) / 1000);
+ return buf;
+}
+
+static void loggerBackend(const Logging::Entry& entry) {
+ static thread_local std::stringstream buf;
+
+ buf.str("");
+ buf << "msg=" << std::quoted(entry.message);
+ if (entry.error) {
+ buf << " oserror=" << std::quoted(entry.error.get());
+ }
+
+ if (entry.name) {
+ buf << " subsystem=" << std::quoted(entry.name.get());
+ }
+ buf << " level=" << entry.level;
+ if (entry.d_priority) {
+ buf << " prio=" << static_cast<int>(entry.d_priority);
+ }
+ char timebuf[64];
+ buf << " ts=" << std::quoted(toTimestampStringMilli(entry.d_timestamp, timebuf, sizeof(timebuf)));
+ for (auto const& v: entry.values) {
+ buf << " ";
+ buf << v.first << "=" << std::quoted(v.second);
+ }
+ Logger::Urgency u = entry.d_priority ? Logger::Urgency(entry.d_priority) : Logger::Info;
+ g_log << u << buf.str() << endl;
+}
+void makeThreadPipes()
+{
+ auto pipeBufferSize = ::arg().asNum("distribution-pipe-buffer-size");
+ if (pipeBufferSize > 0) {
+ g_log<<Logger::Info<<"Resizing the buffer of the distribution pipe to "<<pipeBufferSize<<endl;
+ }
+
+ /* thread 0 is the handler / SNMP, worker threads start at 1 */
+ for(unsigned int n = 0; n <= (g_numWorkerThreads + g_numDistributorThreads); ++n) {
+ auto& threadInfos = s_threadInfos.at(n);
+
+ int fd[2];
+ if(pipe(fd) < 0)
+ unixDie("Creating pipe for inter-thread communications");
+
+ threadInfos.pipes.readToThread = fd[0];
+ threadInfos.pipes.writeToThread = fd[1];
+
+ // handler thread only gets first pipe, not the others
+ if(n==0) {
+ continue;
+ }
+
+ if(pipe(fd) < 0)
+ unixDie("Creating pipe for inter-thread communications");
+
+ threadInfos.pipes.readFromThread = fd[0];
+ threadInfos.pipes.writeFromThread = fd[1];
+
+ if(pipe(fd) < 0)
+ unixDie("Creating pipe for inter-thread communications");
+
+ threadInfos.pipes.readQueriesToThread = fd[0];
+ threadInfos.pipes.writeQueriesToThread = fd[1];
+
+ if (pipeBufferSize > 0) {
+ if (!setPipeBufferSize(threadInfos.pipes.writeQueriesToThread, pipeBufferSize)) {
+ int err = errno;
+ g_log<<Logger::Warning<<"Error resizing the buffer of the distribution pipe for thread "<<n<<" to "<<pipeBufferSize<<": "<<strerror(err)<<endl;
+ auto existingSize = getPipeBufferSize(threadInfos.pipes.writeQueriesToThread);
+ if (existingSize > 0) {
+ g_log<<Logger::Warning<<"The current size of the distribution pipe's buffer for thread "<<n<<" is "<<existingSize<<endl;
+ }
+ }
+ }
+
+ if (!setNonBlocking(threadInfos.pipes.writeQueriesToThread)) {
+ unixDie("Making pipe for inter-thread communications non-blocking");
+ }
+ }
+}
+
+static int ratePercentage(uint64_t nom, uint64_t denom)
+{
+ if (denom == 0) {
+ return 0;
+ }
+ return round(100.0 * nom / denom);
+}
+
+static void doStats(void)
+{
+ static time_t lastOutputTime;
+ static uint64_t lastQueryCount;
+
+ uint64_t cacheHits = g_recCache->cacheHits;
+ uint64_t cacheMisses = g_recCache->cacheMisses;
+ uint64_t cacheSize = g_recCache->size();
+ auto rc_stats = g_recCache->stats();
+ double r = rc_stats.second == 0 ? 0.0 : (100.0 * rc_stats.first / rc_stats.second);
+ uint64_t negCacheSize = g_negCache->size();
+ auto taskPushes = getTaskPushes();
+ auto taskExpired = getTaskExpired();
+ auto taskSize = getTaskSize();
+
+ if(g_stats.qcounter && (cacheHits + cacheMisses) && SyncRes::s_queries && SyncRes::s_outqueries) {
+ g_log<<Logger::Notice<<"stats: "<<g_stats.qcounter<<" questions, "<<
+ cacheSize << " cache entries, "<<
+ negCacheSize<<" negative entries, "<<
+ ratePercentage(cacheHits, cacheHits + cacheMisses)<<"% cache hits"<<endl;
+ g_log << Logger::Notice<< "stats: cache contended/acquired " << rc_stats.first << '/' << rc_stats.second << " = " << r << '%' << endl;
+
+ g_log<<Logger::Notice<<"stats: throttle map: "
+ << broadcastAccFunction<uint64_t>(pleaseGetThrottleSize) <<", ns speeds: "
+ << broadcastAccFunction<uint64_t>(pleaseGetNsSpeedsSize)<<", failed ns: "
+ << broadcastAccFunction<uint64_t>(pleaseGetFailedServersSize)<<", ednsmap: "
+ <<broadcastAccFunction<uint64_t>(pleaseGetEDNSStatusesSize)<<endl;
+ g_log<<Logger::Notice<<"stats: outpacket/query ratio "<<ratePercentage(SyncRes::s_outqueries, SyncRes::s_queries)<<"%";
+ g_log<<Logger::Notice<<", "<<ratePercentage(SyncRes::s_throttledqueries, SyncRes::s_outqueries+SyncRes::s_throttledqueries)<<"% throttled"<<endl;
+ g_log<<Logger::Notice<<"stats: "<<SyncRes::s_tcpoutqueries<<"/"<<SyncRes::s_dotoutqueries << "/" << getCurrentIdleTCPConnections() << " outgoing tcp/dot/idle connections, "<<
+ broadcastAccFunction<uint64_t>(pleaseGetConcurrentQueries)<<" queries running, "<<SyncRes::s_outgoingtimeouts<<" outgoing timeouts "<<endl;
+
+ uint64_t pcSize = broadcastAccFunction<uint64_t>(pleaseGetPacketCacheSize);
+ uint64_t pcHits = broadcastAccFunction<uint64_t>(pleaseGetPacketCacheHits);
+ g_log<<Logger::Notice<<"stats: " << pcSize <<
+ " packet cache entries, "<< ratePercentage(pcHits, SyncRes::s_queries) << "% packet cache hits"<<endl;
+
+ size_t idx = 0;
+ for (const auto& threadInfo : s_threadInfos) {
+ if(threadInfo.isWorker) {
+ g_log<<Logger::Notice<<"stats: thread "<<idx<<" has been distributed "<<threadInfo.numberOfDistributedQueries<<" queries"<<endl;
+ ++idx;
+ }
+ }
+
+ g_log<<Logger::Notice<<"stats: tasks pushed/expired/queuesize: " << taskPushes << '/' << taskExpired << '/' << taskSize << endl;
+ time_t now = time(0);
+ if(lastOutputTime && lastQueryCount && now != lastOutputTime) {
+ g_log<<Logger::Notice<<"stats: "<< (SyncRes::s_queries - lastQueryCount) / (now - lastOutputTime) <<" qps (average over "<< (now - lastOutputTime) << " seconds)"<<endl;
+ }
+ lastOutputTime = now;
+ lastQueryCount = SyncRes::s_queries;
+ }
+ else if(statsWanted)
+ g_log<<Logger::Notice<<"stats: no stats yet!"<<endl;
+
+ statsWanted=false;
+}
+
+static std::shared_ptr<NetmaskGroup> parseACL(const std::string& aclFile, const std::string& aclSetting)
+{
+ auto result = std::make_shared<NetmaskGroup>();
+
+ if(!::arg()[aclFile].empty()) {
+ string line;
+ ifstream ifs(::arg()[aclFile].c_str());
+ if(!ifs) {
+ throw runtime_error("Could not open '"+::arg()[aclFile]+"': "+stringerror());
+ }
+
+ string::size_type pos;
+ while(getline(ifs,line)) {
+ pos=line.find('#');
+ if(pos!=string::npos)
+ line.resize(pos);
+ boost::trim(line);
+ if(line.empty())
+ continue;
+
+ result->addMask(line);
+ }
+ g_log<<Logger::Info<<"Done parsing "<<result->size()<<" "<<aclSetting<<" ranges from file '"<<::arg()[aclFile]<<"' - overriding '"<<aclSetting<<"' setting"<<endl;
+
+ return result;
+ }
+ else if(!::arg()[aclSetting].empty()) {
+ vector<string> ips;
+ stringtok(ips, ::arg()[aclSetting], ", ");
+
+ g_log<<Logger::Info<<aclSetting<<": ";
+ for(vector<string>::const_iterator i = ips.begin(); i!= ips.end(); ++i) {
+ result->addMask(*i);
+ if(i!=ips.begin())
+ g_log<<Logger::Info<<", ";
+ g_log<<Logger::Info<<*i;
+ }
+ g_log<<Logger::Info<<endl;
+
+ return result;
+ }
+
+ return nullptr;
+}
+
+static void* pleaseSupplantAllowFrom(std::shared_ptr<NetmaskGroup> ng)
+{
+ t_allowFrom = ng;
+ return nullptr;
+}
+
+static void* pleaseSupplantAllowNotifyFrom(std::shared_ptr<NetmaskGroup> ng)
+{
+ t_allowNotifyFrom = ng;
+ return nullptr;
+}
+
+void* pleaseSupplantAllowNotifyFor(std::shared_ptr<notifyset_t> ns)
+{
+ t_allowNotifyFor = ns;
+ return nullptr;
+}
+
+void parseACLs()
+{
+ static bool l_initialized;
+
+ if(l_initialized) { // only reload configuration file on second call
+ string configname=::arg()["config-dir"]+"/recursor.conf";
+ if(::arg()["config-name"]!="") {
+ configname=::arg()["config-dir"]+"/recursor-"+::arg()["config-name"]+".conf";
+ }
+ cleanSlashes(configname);
+
+ if(!::arg().preParseFile(configname.c_str(), "allow-from-file"))
+ throw runtime_error("Unable to re-parse configuration file '"+configname+"'");
+ ::arg().preParseFile(configname.c_str(), "allow-from", LOCAL_NETS);
+
+ if(!::arg().preParseFile(configname.c_str(), "allow-notify-from-file"))
+ throw runtime_error("Unable to re-parse configuration file '"+configname+"'");
+ ::arg().preParseFile(configname.c_str(), "allow-notify-from");
+
+ ::arg().preParseFile(configname.c_str(), "include-dir");
+ ::arg().preParse(g_argc, g_argv, "include-dir");
+
+ // then process includes
+ std::vector<std::string> extraConfigs;
+ ::arg().gatherIncludes(extraConfigs);
+
+ for(const std::string& fn : extraConfigs) {
+ if(!::arg().preParseFile(fn.c_str(), "allow-from-file", ::arg()["allow-from-file"]))
+ throw runtime_error("Unable to re-parse configuration file include '"+fn+"'");
+ if(!::arg().preParseFile(fn.c_str(), "allow-from", ::arg()["allow-from"]))
+ throw runtime_error("Unable to re-parse configuration file include '"+fn+"'");
+
+ if(!::arg().preParseFile(fn.c_str(), "allow-notify-from-file", ::arg()["allow-notify-from-file"]))
+ throw runtime_error("Unable to re-parse configuration file include '"+fn+"'");
+ if(!::arg().preParseFile(fn.c_str(), "allow-notify-from", ::arg()["allow-notify-from"]))
+ throw runtime_error("Unable to re-parse configuration file include '"+fn+"'");
+ }
+
+ ::arg().preParse(g_argc, g_argv, "allow-from-file");
+ ::arg().preParse(g_argc, g_argv, "allow-from");
+
+ ::arg().preParse(g_argc, g_argv, "allow-notify-from-file");
+ ::arg().preParse(g_argc, g_argv, "allow-notify-from");
+ }
+
+ auto allowFrom = parseACL("allow-from-file", "allow-from");
+
+ if(allowFrom->size() == 0) {
+ if(::arg()["local-address"]!="127.0.0.1" && ::arg().asNum("local-port")==53)
+ g_log<<Logger::Warning<<"WARNING: Allowing queries from all IP addresses - this can be a security risk!"<<endl;
+ allowFrom = nullptr;
+ }
+
+ g_initialAllowFrom = allowFrom;
+ broadcastFunction([=]{ return pleaseSupplantAllowFrom(allowFrom); });
+
+ auto allowNotifyFrom = parseACL("allow-notify-from-file", "allow-notify-from");
+
+ g_initialAllowNotifyFrom = allowNotifyFrom;
+ broadcastFunction([=]{ return pleaseSupplantAllowNotifyFrom(allowNotifyFrom); });
+
+ l_initialized = true;
+}
+
+
+void broadcastFunction(const pipefunc_t& func)
+{
+ /* This function might be called by the worker with t_id 0 during startup
+ for the initialization of ACLs and domain maps. After that it should only
+ be called by the handler. */
+
+ if (s_threadInfos.empty() && isHandlerThread()) {
+ /* the handler and distributors will call themselves below, but
+ during startup we get called while s_threadInfos has not been
+ populated yet to update the ACL or domain maps, so we need to
+ handle that case.
+ */
+ func();
+ }
+
+ unsigned int n = 0;
+ for (const auto& threadInfo : s_threadInfos) {
+ if(n++ == t_id) {
+ func(); // don't write to ourselves!
+ continue;
+ }
+
+ ThreadMSG* tmsg = new ThreadMSG();
+ tmsg->func = func;
+ tmsg->wantAnswer = true;
+ if(write(threadInfo.pipes.writeToThread, &tmsg, sizeof(tmsg)) != sizeof(tmsg)) {
+ delete tmsg;
+
+ unixDie("write to thread pipe returned wrong size or error");
+ }
+
+ string* resp = nullptr;
+ if(read(threadInfo.pipes.readFromThread, &resp, sizeof(resp)) != sizeof(resp))
+ unixDie("read from thread pipe returned wrong size or error");
+
+ if(resp) {
+ delete resp;
+ resp = nullptr;
+ }
+ }
+}
+
+template<class T> void *voider(const boost::function<T*()>& func)
+{
+ return func();
+}
+
+static vector<ComboAddress>& operator+=(vector<ComboAddress>&a, const vector<ComboAddress>& b)
+{
+ a.insert(a.end(), b.begin(), b.end());
+ return a;
+}
+
+static vector<pair<DNSName, uint16_t> >& operator+=(vector<pair<DNSName, uint16_t> >&a, const vector<pair<DNSName, uint16_t> >& b)
+{
+ a.insert(a.end(), b.begin(), b.end());
+ return a;
+}
+
+/*
+ This function should only be called by the handler to gather metrics, wipe the cache,
+ reload the Lua script (not the Lua config) or change the current trace regex,
+ and by the SNMP thread to gather metrics. */
+template<class T> T broadcastAccFunction(const boost::function<T*()>& func)
+{
+ if (!isHandlerThread()) {
+ g_log<<Logger::Error<<"broadcastAccFunction has been called by a worker ("<<t_id<<")"<<endl;
+ _exit(1);
+ }
+
+ unsigned int n = 0;
+ T ret=T();
+ for (const auto& threadInfo : s_threadInfos) {
+ if (n++ == t_id) {
+ continue;
+ }
+
+ const auto& tps = threadInfo.pipes;
+ ThreadMSG* tmsg = new ThreadMSG();
+ tmsg->func = [func]{ return voider<T>(func); };
+ tmsg->wantAnswer = true;
+
+ if(write(tps.writeToThread, &tmsg, sizeof(tmsg)) != sizeof(tmsg)) {
+ delete tmsg;
+ unixDie("write to thread pipe returned wrong size or error");
+ }
+
+ T* resp = nullptr;
+ if(read(tps.readFromThread, &resp, sizeof(resp)) != sizeof(resp))
+ unixDie("read from thread pipe returned wrong size or error");
+
+ if(resp) {
+ ret += *resp;
+ delete resp;
+ resp = nullptr;
+ }
+ }
+ return ret;
+}
+
+template string broadcastAccFunction(const boost::function<string*()>& fun); // explicit instantiation
+template RecursorControlChannel::Answer broadcastAccFunction(const boost::function<RecursorControlChannel::Answer *()>& fun); // explicit instantiation
+template uint64_t broadcastAccFunction(const boost::function<uint64_t*()>& fun); // explicit instantiation
+template vector<ComboAddress> broadcastAccFunction(const boost::function<vector<ComboAddress> *()>& fun); // explicit instantiation
+template vector<pair<DNSName,uint16_t> > broadcastAccFunction(const boost::function<vector<pair<DNSName, uint16_t> > *()>& fun); // explicit instantiation
+template ThreadTimes broadcastAccFunction(const boost::function<ThreadTimes*()>& fun);
+
+static int serviceMain(int argc, char*argv[])
+{
+ int ret = EXIT_SUCCESS;
+
+ g_slogStructured = ::arg().mustDo("structured-logging");
+
+ g_log.setName(s_programname);
+ g_log.disableSyslog(::arg().mustDo("disable-syslog"));
+ g_log.setTimestamps(::arg().mustDo("log-timestamp"));
+
+ if(!::arg()["logging-facility"].empty()) {
+ int val=logFacilityToLOG(::arg().asNum("logging-facility") );
+ if(val >= 0)
+ g_log.setFacility(val);
+ else
+ g_log<<Logger::Error<<"Unknown logging facility "<<::arg().asNum("logging-facility") <<endl;
+ }
+
+ showProductVersion();
+
+ g_disthashseed=dns_random(0xffffffff);
+
+ checkLinuxIPv6Limits();
+ try {
+ pdns::parseQueryLocalAddress(::arg()["query-local-address"]);
+ }
+ catch(std::exception& e) {
+ g_log<<Logger::Error<<"Assigning local query addresses: "<<e.what();
+ exit(99);
+ }
+
+ if(pdns::isQueryLocalAddressFamilyEnabled(AF_INET)) {
+ SyncRes::s_doIPv4=true;
+ g_log<<Logger::Warning<<"Enabling IPv4 transport for outgoing queries"<<endl;
+ }
+ else {
+ g_log<<Logger::Warning<<"NOT using IPv4 for outgoing queries - add an IPv4 address (like '0.0.0.0') to query-local-address to enable"<<endl;
+ }
+
+
+ if(pdns::isQueryLocalAddressFamilyEnabled(AF_INET6)) {
+ SyncRes::s_doIPv6=true;
+ g_log<<Logger::Warning<<"Enabling IPv6 transport for outgoing queries"<<endl;
+ }
+ else {
+ g_log<<Logger::Warning<<"NOT using IPv6 for outgoing queries - add an IPv6 address (like '::') to query-local-address to enable"<<endl;
+ }
+
+ if (!SyncRes::s_doIPv6 && !SyncRes::s_doIPv4) {
+ g_log<<Logger::Error<<"No outgoing addresses configured! Can not continue"<<endl;
+ exit(99);
+ }
+
+ // keep this ABOVE loadRecursorLuaConfig!
+ if(::arg()["dnssec"]=="off")
+ g_dnssecmode=DNSSECMode::Off;
+ else if(::arg()["dnssec"]=="process-no-validate")
+ g_dnssecmode=DNSSECMode::ProcessNoValidate;
+ else if(::arg()["dnssec"]=="process")
+ g_dnssecmode=DNSSECMode::Process;
+ else if(::arg()["dnssec"]=="validate")
+ g_dnssecmode=DNSSECMode::ValidateAll;
+ else if(::arg()["dnssec"]=="log-fail")
+ g_dnssecmode=DNSSECMode::ValidateForLog;
+ else {
+ g_log<<Logger::Error<<"Unknown DNSSEC mode "<<::arg()["dnssec"]<<endl;
+ exit(1);
+ }
+
+ g_signatureInceptionSkew = ::arg().asNum("signature-inception-skew");
+ if (g_signatureInceptionSkew < 0) {
+ g_log<<Logger::Error<<"A negative value for 'signature-inception-skew' is not allowed"<<endl;
+ exit(1);
+ }
+
+ g_dnssecLogBogus = ::arg().mustDo("dnssec-log-bogus");
+ g_maxNSEC3Iterations = ::arg().asNum("nsec3-max-iterations");
+
+ g_maxCacheEntries = ::arg().asNum("max-cache-entries");
+ g_maxPacketCacheEntries = ::arg().asNum("max-packetcache-entries");
+
+ luaConfigDelayedThreads delayedLuaThreads;
+ try {
+ loadRecursorLuaConfig(::arg()["lua-config-file"], delayedLuaThreads);
+ }
+ catch (PDNSException &e) {
+ g_log<<Logger::Error<<"Cannot load Lua configuration: "<<e.reason<<endl;
+ exit(1);
+ }
+
+ parseACLs();
+ initPublicSuffixList(::arg()["public-suffix-list-file"]);
+
+ if(!::arg()["dont-query"].empty()) {
+ vector<string> ips;
+ stringtok(ips, ::arg()["dont-query"], ", ");
+ ips.push_back("0.0.0.0");
+ ips.push_back("::");
+
+ g_log<<Logger::Warning<<"Will not send queries to: ";
+ for(vector<string>::const_iterator i = ips.begin(); i!= ips.end(); ++i) {
+ SyncRes::addDontQuery(*i);
+ if(i!=ips.begin())
+ g_log<<Logger::Warning<<", ";
+ g_log<<Logger::Warning<<*i;
+ }
+ g_log<<Logger::Warning<<endl;
+ }
+
+ /* this needs to be done before parseACLs(), which call broadcastFunction() */
+ g_weDistributeQueries = ::arg().mustDo("pdns-distributes-queries");
+ if(g_weDistributeQueries) {
+ g_log<<Logger::Warning<<"PowerDNS Recursor itself will distribute queries over threads"<<endl;
+ }
+
+ g_outgoingEDNSBufsize=::arg().asNum("edns-outgoing-bufsize");
+
+ if(::arg()["trace"]=="fail") {
+ SyncRes::setDefaultLogMode(SyncRes::Store);
+ }
+ else if(::arg().mustDo("trace")) {
+ SyncRes::setDefaultLogMode(SyncRes::Log);
+ ::arg().set("quiet")="no";
+ g_quiet=false;
+ g_dnssecLOG=true;
+ }
+ string myHostname = getHostname();
+ if (myHostname == "UNKNOWN"){
+ g_log<<Logger::Warning<<"Unable to get the hostname, NSID and id.server values will be empty"<<endl;
+ myHostname = "";
+ }
+
+ SyncRes::s_minimumTTL = ::arg().asNum("minimum-ttl-override");
+ SyncRes::s_minimumECSTTL = ::arg().asNum("ecs-minimum-ttl-override");
+
+ SyncRes::s_nopacketcache = ::arg().mustDo("disable-packetcache");
+
+ SyncRes::s_maxnegttl=::arg().asNum("max-negative-ttl");
+ SyncRes::s_maxbogusttl=::arg().asNum("max-cache-bogus-ttl");
+ SyncRes::s_maxcachettl=max(::arg().asNum("max-cache-ttl"), 15);
+ SyncRes::s_packetcachettl=::arg().asNum("packetcache-ttl");
+ // Cap the packetcache-servfail-ttl to the packetcache-ttl
+ uint32_t packetCacheServFailTTL = ::arg().asNum("packetcache-servfail-ttl");
+ SyncRes::s_packetcacheservfailttl=(packetCacheServFailTTL > SyncRes::s_packetcachettl) ? SyncRes::s_packetcachettl : packetCacheServFailTTL;
+ SyncRes::s_serverdownmaxfails=::arg().asNum("server-down-max-fails");
+ SyncRes::s_serverdownthrottletime=::arg().asNum("server-down-throttle-time");
+ SyncRes::s_nonresolvingnsmaxfails=::arg().asNum("non-resolving-ns-max-fails");
+ SyncRes::s_nonresolvingnsthrottletime=::arg().asNum("non-resolving-ns-throttle-time");
+ SyncRes::s_serverID=::arg()["server-id"];
+ SyncRes::s_maxqperq=::arg().asNum("max-qperq");
+ SyncRes::s_maxnsaddressqperq=::arg().asNum("max-ns-address-qperq");
+ SyncRes::s_maxtotusec=1000*::arg().asNum("max-total-msec");
+ SyncRes::s_maxdepth=::arg().asNum("max-recursion-depth");
+ SyncRes::s_rootNXTrust = ::arg().mustDo( "root-nx-trust");
+ SyncRes::s_refresh_ttlperc = ::arg().asNum("refresh-on-ttl-perc");
+ RecursorPacketCache::s_refresh_ttlperc = SyncRes::s_refresh_ttlperc;
+ SyncRes::s_tcp_fast_open = ::arg().asNum("tcp-fast-open");
+ SyncRes::s_tcp_fast_open_connect = ::arg().mustDo("tcp-fast-open-connect");
+
+ SyncRes::s_dot_to_port_853 = ::arg().mustDo("dot-to-port-853");
+ SyncRes::s_event_trace_enabled = ::arg().asNum("event-trace-enabled");
+
+ if (SyncRes::s_tcp_fast_open_connect) {
+ checkFastOpenSysctl(true);
+ checkTFOconnect();
+ }
+
+ if(SyncRes::s_serverID.empty()) {
+ SyncRes::s_serverID = myHostname;
+ }
+
+ SyncRes::s_ecsipv4limit = ::arg().asNum("ecs-ipv4-bits");
+ SyncRes::s_ecsipv6limit = ::arg().asNum("ecs-ipv6-bits");
+ SyncRes::clearECSStats();
+ SyncRes::s_ecsipv4cachelimit = ::arg().asNum("ecs-ipv4-cache-bits");
+ SyncRes::s_ecsipv6cachelimit = ::arg().asNum("ecs-ipv6-cache-bits");
+ SyncRes::s_ecsipv4nevercache = ::arg().mustDo("ecs-ipv4-never-cache");
+ SyncRes::s_ecsipv6nevercache = ::arg().mustDo("ecs-ipv6-never-cache");
+ SyncRes::s_ecscachelimitttl = ::arg().asNum("ecs-cache-limit-ttl");
+
+ SyncRes::s_qnameminimization = ::arg().mustDo("qname-minimization");
+
+ if (SyncRes::s_qnameminimization) {
+ // With an empty cache, a rev ipv6 query with dnssec enabled takes
+ // almost 100 queries. Default maxqperq is 60.
+ SyncRes::s_maxqperq = std::max(SyncRes::s_maxqperq, static_cast<unsigned int>(100));
+ }
+
+ SyncRes::s_hardenNXD = SyncRes::HardenNXD::DNSSEC;
+ string value = ::arg()["nothing-below-nxdomain"];
+ if (value == "yes") {
+ SyncRes::s_hardenNXD = SyncRes::HardenNXD::Yes;
+ } else if (value == "no") {
+ SyncRes::s_hardenNXD = SyncRes::HardenNXD::No;
+ } else if (value != "dnssec") {
+ g_log << Logger::Error << "Unknown nothing-below-nxdomain mode: " << value << endl;
+ exit(1);
+ }
+
+ if (!::arg().isEmpty("ecs-scope-zero-address")) {
+ ComboAddress scopeZero(::arg()["ecs-scope-zero-address"]);
+ SyncRes::setECSScopeZeroAddress(Netmask(scopeZero, scopeZero.isIPv4() ? 32 : 128));
+ }
+ else {
+ Netmask nm;
+ bool done = false;
+
+ auto addr = pdns::getNonAnyQueryLocalAddress(AF_INET);
+ if (addr.sin4.sin_family != 0) {
+ nm = Netmask(addr, 32);
+ done = true;
+ }
+ if (!done) {
+ addr = pdns::getNonAnyQueryLocalAddress(AF_INET6);
+ if (addr.sin4.sin_family != 0) {
+ nm = Netmask(addr, 128);
+ done = true;
+ }
+ }
+ if (!done) {
+ nm = Netmask(ComboAddress("127.0.0.1"), 32);
+ }
+ SyncRes::setECSScopeZeroAddress(nm);
+ }
+
+ SyncRes::parseEDNSSubnetAllowlist(::arg()["edns-subnet-whitelist"]);
+ SyncRes::parseEDNSSubnetAllowlist(::arg()["edns-subnet-allow-list"]);
+ SyncRes::parseEDNSSubnetAddFor(::arg()["ecs-add-for"]);
+ g_useIncomingECS = ::arg().mustDo("use-incoming-edns-subnet");
+
+ g_XPFAcl.toMasks(::arg()["xpf-allow-from"]);
+ g_xpfRRCode = ::arg().asNum("xpf-rr-code");
+
+ g_proxyProtocolACL.toMasks(::arg()["proxy-protocol-from"]);
+ g_proxyProtocolMaximumSize = ::arg().asNum("proxy-protocol-maximum-size");
+
+ if (!::arg()["dns64-prefix"].empty()) {
+ try {
+ auto dns64Prefix = Netmask(::arg()["dns64-prefix"]);
+ if (dns64Prefix.getBits() != 96) {
+ g_log << Logger::Error << "Invalid prefix for 'dns64-prefix', the current implementation only supports /96 prefixes: " << ::arg()["dns64-prefix"] << endl;
+ exit(1);
+ }
+ g_dns64Prefix = dns64Prefix.getNetwork();
+ g_dns64PrefixReverse = reverseNameFromIP(*g_dns64Prefix);
+ /* /96 is 24 nibbles + 2 for "ip6.arpa." */
+ while (g_dns64PrefixReverse.countLabels() > 26) {
+ g_dns64PrefixReverse.chopOff();
+ }
+ }
+ catch (const NetmaskException& ne) {
+ g_log << Logger::Error << "Invalid prefix '" << ::arg()["dns64-prefix"] << "' for 'dns64-prefix': " << ne.reason << endl;
+ exit(1);
+ }
+ }
+
+ g_networkTimeoutMsec = ::arg().asNum("network-timeout");
+
+ std::tie(g_initialDomainMap, g_initialAllowNotifyFor) = parseZoneConfiguration();
+
+ g_latencyStatSize=::arg().asNum("latency-statistic-size");
+
+ g_logCommonErrors=::arg().mustDo("log-common-errors");
+ g_logRPZChanges = ::arg().mustDo("log-rpz-changes");
+
+ g_anyToTcp = ::arg().mustDo("any-to-tcp");
+ g_udpTruncationThreshold = ::arg().asNum("udp-truncation-threshold");
+
+ g_lowercaseOutgoing = ::arg().mustDo("lowercase-outgoing");
+
+ g_paddingFrom.toMasks(::arg()["edns-padding-from"]);
+ if (::arg()["edns-padding-mode"] == "always") {
+ g_paddingMode = PaddingMode::Always;
+ }
+ else if (::arg()["edns-padding-mode"] == "padded-queries-only") {
+ g_paddingMode = PaddingMode::PaddedQueries;
+ }
+ else {
+ g_log << Logger::Error << "Unknown edns-padding-mode: " << ::arg()["edns-padding-mode"] << endl;
+ exit(1);
+ }
+ g_paddingTag = ::arg().asNum("edns-padding-tag");
+
+ g_numDistributorThreads = ::arg().asNum("distributor-threads");
+ g_numWorkerThreads = ::arg().asNum("threads");
+ if (g_numWorkerThreads < 1) {
+ g_log<<Logger::Warning<<"Asked to run with 0 threads, raising to 1 instead"<<endl;
+ g_numWorkerThreads = 1;
+ }
+
+ g_numThreads = g_numDistributorThreads + g_numWorkerThreads;
+ g_maxMThreads = ::arg().asNum("max-mthreads");
+
+
+ int64_t maxInFlight = ::arg().asNum("max-concurrent-requests-per-tcp-connection");
+ if (maxInFlight < 1 || maxInFlight > USHRT_MAX || maxInFlight >= g_maxMThreads) {
+ g_log<<Logger::Warning<<"Asked to run with illegal max-concurrent-requests-per-tcp-connection, setting to default (10)"<<endl;
+ TCPConnection::s_maxInFlight = 10;
+ } else {
+ TCPConnection::s_maxInFlight = maxInFlight;
+ }
+
+ int64_t millis = ::arg().asNum("tcp-out-max-idle-ms");
+ TCPOutConnectionManager::s_maxIdleTime = timeval{millis / 1000, (static_cast<suseconds_t>(millis) % 1000) * 1000 };
+ TCPOutConnectionManager::s_maxIdlePerAuth = ::arg().asNum("tcp-out-max-idle-per-auth");
+ TCPOutConnectionManager::s_maxQueries = ::arg().asNum("tcp-out-max-queries");
+ TCPOutConnectionManager::s_maxIdlePerThread = ::arg().asNum("tcp-out-max-idle-per-thread");
+
+ g_gettagNeedsEDNSOptions = ::arg().mustDo("gettag-needs-edns-options");
+
+ g_statisticsInterval = ::arg().asNum("statistics-interval");
+
+ s_addExtendedResolutionDNSErrors = ::arg().mustDo("extended-resolution-errors");
+
+ if (::arg().asNum("aggressive-nsec-cache-size") > 0) {
+ if (g_dnssecmode == DNSSECMode::ValidateAll || g_dnssecmode == DNSSECMode::ValidateForLog || g_dnssecmode == DNSSECMode::Process) {
+ g_aggressiveNSECCache = make_unique<AggressiveNSECCache>(::arg().asNum("aggressive-nsec-cache-size"));
+ }
+ else {
+ g_log<<Logger::Warning<<"Aggressive NSEC/NSEC3 caching is enabled but DNSSEC validation is not set to 'validate', 'log-fail' or 'process', ignoring"<<endl;
+ }
+ }
+
+ {
+ SuffixMatchNode dontThrottleNames;
+ vector<string> parts;
+ stringtok(parts, ::arg()["dont-throttle-names"], " ,");
+ for (const auto &p : parts) {
+ dontThrottleNames.add(DNSName(p));
+ }
+ g_dontThrottleNames.setState(std::move(dontThrottleNames));
+
+ parts.clear();
+ NetmaskGroup dontThrottleNetmasks;
+ stringtok(parts, ::arg()["dont-throttle-netmasks"], " ,");
+ for (const auto &p : parts) {
+ dontThrottleNetmasks.addMask(Netmask(p));
+ }
+ g_dontThrottleNetmasks.setState(std::move(dontThrottleNetmasks));
+ }
+
+ {
+ SuffixMatchNode xdnssecNames;
+ vector<string> parts;
+ stringtok(parts, ::arg()["x-dnssec-names"], " ,");
+ for (const auto &p : parts) {
+ xdnssecNames.add(DNSName(p));
+ }
+ g_xdnssec.setState(std::move(xdnssecNames));
+ }
+
+ {
+ SuffixMatchNode dotauthNames;
+ vector<string> parts;
+ stringtok(parts, ::arg()["dot-to-auth-names"], " ,");
+#ifndef HAVE_DNS_OVER_TLS
+ if (parts.size()) {
+ g_log << Logger::Error << "dot-to-auth-names setting contains names, but Recursor was built without DNS over TLS support. Setting will be ignored."<<endl;
+ }
+#endif
+ for (const auto &p : parts) {
+ dotauthNames.add(DNSName(p));
+ }
+ g_DoTToAuthNames.setState(std::move(dotauthNames));
+ }
+
+ {
+ CarbonConfig config;
+ stringtok(config.servers, arg()["carbon-server"], ", ");
+ config.hostname = arg()["carbon-ourname"];
+ config.instance_name = arg()["carbon-instance"];
+ config.namespace_name = arg()["carbon-namespace"];
+ g_carbonConfig.setState(std::move(config));
+ }
+
+ s_balancingFactor = ::arg().asDouble("distribution-load-factor");
+ if (s_balancingFactor != 0.0 && s_balancingFactor < 1.0) {
+ s_balancingFactor = 0.0;
+ g_log<<Logger::Warning<<"Asked to run with a distribution-load-factor below 1.0, disabling it instead"<<endl;
+ }
+
+#ifdef SO_REUSEPORT
+ g_reusePort = ::arg().mustDo("reuseport");
+#endif
+
+ s_threadInfos.resize(g_numDistributorThreads + g_numWorkerThreads + /* handler */ 1);
+
+ if (g_reusePort) {
+ if (g_weDistributeQueries) {
+ /* first thread is the handler, then distributors */
+ for (unsigned int threadId = 1; threadId <= g_numDistributorThreads; threadId++) {
+ auto& deferredAdds = s_threadInfos.at(threadId).deferredAdds;
+ auto& tcpSockets = s_threadInfos.at(threadId).tcpSockets;
+ makeUDPServerSockets(deferredAdds);
+ makeTCPServerSockets(deferredAdds, tcpSockets);
+ }
+ }
+ else {
+ /* first thread is the handler, there is no distributor here and workers are accepting queries */
+ for (unsigned int threadId = 1; threadId <= g_numWorkerThreads; threadId++) {
+ auto& deferredAdds = s_threadInfos.at(threadId).deferredAdds;
+ auto& tcpSockets = s_threadInfos.at(threadId).tcpSockets;
+ makeUDPServerSockets(deferredAdds);
+ makeTCPServerSockets(deferredAdds, tcpSockets);
+ }
+ }
+ }
+ else {
+ std::set<int> tcpSockets;
+ /* we don't have reuseport so we can only open one socket per
+ listening addr:port and everyone will listen on it */
+ makeUDPServerSockets(g_deferredAdds);
+ makeTCPServerSockets(g_deferredAdds, tcpSockets);
+
+ /* every listener (so distributor if g_weDistributeQueries, workers otherwise)
+ needs to listen to the shared sockets */
+ if (g_weDistributeQueries) {
+ /* first thread is the handler, then distributors */
+ for (unsigned int threadId = 1; threadId <= g_numDistributorThreads; threadId++) {
+ s_threadInfos.at(threadId).tcpSockets = tcpSockets;
+ }
+ }
+ else {
+ /* first thread is the handler, there is no distributor here and workers are accepting queries */
+ for (unsigned int threadId = 1; threadId <= g_numWorkerThreads; threadId++) {
+ s_threadInfos.at(threadId).tcpSockets = tcpSockets;
+ }
+ }
+ }
+
+#ifdef NOD_ENABLED
+ // Setup newly observed domain globals
+ setupNODGlobal();
+#endif /* NOD_ENABLED */
+
+ int forks;
+ for(forks = 0; forks < ::arg().asNum("processes") - 1; ++forks) {
+ if(!fork()) // we are child
+ break;
+ }
+
+ if(::arg().mustDo("daemon")) {
+ g_log<<Logger::Warning<<"Calling daemonize, going to background"<<endl;
+ g_log.toConsole(Logger::Critical);
+ daemonize();
+ }
+ if(Utility::getpid() == 1) {
+ /* We are running as pid 1, register sigterm and sigint handler
+
+ The Linux kernel will handle SIGTERM and SIGINT for all processes, except PID 1.
+ It assumes that the processes running as pid 1 is an "init" like system.
+ For years, this was a safe assumption, but containers change that: in
+ most (all?) container implementations, the application itself is running
+ as pid 1. This means that sending signals to those applications, will not
+ be handled by default. Results might be "your container not responding
+ when asking it to stop", or "ctrl-c not working even when the app is
+ running in the foreground inside a container".
+
+ So TL;DR: If we're running pid 1 (container), we should handle SIGTERM and SIGINT ourselves */
+
+ signal(SIGTERM,termIntHandler);
+ signal(SIGINT,termIntHandler);
+ }
+
+ signal(SIGUSR1,usr1Handler);
+ signal(SIGUSR2,usr2Handler);
+ signal(SIGPIPE,SIG_IGN);
+
+ checkOrFixFDS();
+
+#ifdef HAVE_LIBSODIUM
+ if (sodium_init() == -1) {
+ g_log<<Logger::Error<<"Unable to initialize sodium crypto library"<<endl;
+ exit(99);
+ }
+#endif
+
+ openssl_thread_setup();
+ openssl_seed();
+ /* setup rng before chroot */
+ dns_random_init();
+
+ if(::arg()["server-id"].empty()) {
+ ::arg().set("server-id") = myHostname;
+ }
+
+ int newgid=0;
+ if(!::arg()["setgid"].empty())
+ newgid = strToGID(::arg()["setgid"]);
+ int newuid=0;
+ if(!::arg()["setuid"].empty())
+ newuid = strToUID(::arg()["setuid"]);
+
+ Utility::dropGroupPrivs(newuid, newgid);
+
+ if (!::arg()["chroot"].empty()) {
+#ifdef HAVE_SYSTEMD
+ char *ns;
+ ns = getenv("NOTIFY_SOCKET");
+ if (ns != nullptr) {
+ g_log<<Logger::Error<<"Unable to chroot when running from systemd. Please disable chroot= or set the 'Type' for this service to 'simple'"<<endl;
+ exit(1);
+ }
+#endif
+ if (chroot(::arg()["chroot"].c_str())<0 || chdir("/") < 0) {
+ int err = errno;
+ g_log<<Logger::Error<<"Unable to chroot to '"+::arg()["chroot"]+"': "<<strerror (err)<<", exiting"<<endl;
+ exit(1);
+ }
+ else
+ g_log<<Logger::Info<<"Chrooted to '"<<::arg()["chroot"]<<"'"<<endl;
+ }
+
+ checkSocketDir();
+
+ s_pidfname=::arg()["socket-dir"]+"/"+s_programname+".pid";
+ if(!s_pidfname.empty())
+ unlink(s_pidfname.c_str()); // remove possible old pid file
+ writePid();
+
+ makeControlChannelSocket( ::arg().asNum("processes") > 1 ? forks : -1);
+
+ Utility::dropUserPrivs(newuid);
+ try {
+ /* we might still have capabilities remaining, for example if we have been started as root
+ without --setuid (please don't do that) or as an unprivileged user with ambient capabilities
+ like CAP_NET_BIND_SERVICE.
+ */
+ dropCapabilities();
+ }
+ catch(const std::exception& e) {
+ g_log<<Logger::Warning<<e.what()<<endl;
+ }
+
+ startLuaConfigDelayedThreads(delayedLuaThreads, g_luaconfs.getCopy().generation);
+ delayedLuaThreads.rpzPrimaryThreads.clear(); // no longer needed
+ delayedLuaThreads.ztcConfigs.clear(); // no longer needed
+
+ makeThreadPipes();
+
+ g_tcpTimeout=::arg().asNum("client-tcp-timeout");
+ g_maxTCPPerClient=::arg().asNum("max-tcp-per-client");
+ g_tcpMaxQueriesPerConn=::arg().asNum("max-tcp-queries-per-connection");
+ s_maxUDPQueriesPerRound=::arg().asNum("max-udp-queries-per-round");
+
+ g_useKernelTimestamp = ::arg().mustDo("protobuf-use-kernel-timestamp");
+
+ disableStats(StatComponent::API, ::arg()["stats-api-blacklist"]);
+ disableStats(StatComponent::Carbon, ::arg()["stats-carbon-blacklist"]);
+ disableStats(StatComponent::RecControl, ::arg()["stats-rec-control-blacklist"]);
+ disableStats(StatComponent::SNMP, ::arg()["stats-snmp-blacklist"]);
+
+ disableStats(StatComponent::API, ::arg()["stats-api-disabled-list"]);
+ disableStats(StatComponent::Carbon, ::arg()["stats-carbon-disabled-list"]);
+ disableStats(StatComponent::RecControl, ::arg()["stats-rec-control-disabled-list"]);
+ disableStats(StatComponent::SNMP, ::arg()["stats-snmp-disabled-list"]);
+
+ if (::arg().mustDo("snmp-agent")) {
+ string setting = ::arg()["snmp-daemon-socket"];
+ if (setting.empty()) {
+ setting = ::arg()["snmp-master-socket"];
+ }
+ g_snmpAgent = std::make_shared<RecursorSNMPAgent>("recursor", setting);
+ g_snmpAgent->run();
+ }
+
+ int port = ::arg().asNum("udp-source-port-min");
+ if(port < 1024 || port > 65535){
+ g_log<<Logger::Error<<"Unable to launch, udp-source-port-min is not a valid port number"<<endl;
+ exit(99); // this isn't going to fix itself either
+ }
+ s_minUdpSourcePort = port;
+ port = ::arg().asNum("udp-source-port-max");
+ if(port < 1024 || port > 65535 || port < s_minUdpSourcePort){
+ g_log<<Logger::Error<<"Unable to launch, udp-source-port-max is not a valid port number or is smaller than udp-source-port-min"<<endl;
+ exit(99); // this isn't going to fix itself either
+ }
+ s_maxUdpSourcePort = port;
+ std::vector<string> parts {};
+ stringtok(parts, ::arg()["udp-source-port-avoid"], ", ");
+ for (const auto &part : parts)
+ {
+ port = std::stoi(part);
+ if(port < 1024 || port > 65535){
+ g_log<<Logger::Error<<"Unable to launch, udp-source-port-avoid contains an invalid port number: "<<part<<endl;
+ exit(99); // this isn't going to fix itself either
+ }
+ s_avoidUdpSourcePorts.insert(port);
+ }
+
+ unsigned int currentThreadId = 1;
+ const auto cpusMap = parseCPUMap();
+
+ if(g_numThreads == 1) {
+ g_log<<Logger::Warning<<"Operating unthreaded"<<endl;
+#ifdef HAVE_SYSTEMD
+ sd_notify(0, "READY=1");
+#endif
+
+ /* This thread handles the web server, carbon, statistics and the control channel */
+ auto& handlerInfos = s_threadInfos.at(0);
+ handlerInfos.isHandler = true;
+ handlerInfos.thread = std::thread(recursorThread, 0, "web+stat");
+
+ setCPUMap(cpusMap, currentThreadId, pthread_self());
+
+ auto& infos = s_threadInfos.at(currentThreadId);
+ infos.isListener = true;
+ infos.isWorker = true;
+ recursorThread(currentThreadId++, "worker");
+
+ handlerInfos.thread.join();
+ if (handlerInfos.exitCode != 0) {
+ ret = handlerInfos.exitCode;
+ }
+ }
+ else {
+
+
+ if (g_weDistributeQueries) {
+ for(unsigned int n=0; n < g_numDistributorThreads; ++n) {
+ auto& infos = s_threadInfos.at(currentThreadId + n);
+ infos.isListener = true;
+ }
+ }
+ for(unsigned int n=0; n < g_numWorkerThreads; ++n) {
+ auto& infos = s_threadInfos.at(currentThreadId + (g_weDistributeQueries ? g_numDistributorThreads : 0) + n);
+ infos.isListener = !g_weDistributeQueries;
+ infos.isWorker = true;
+ }
+
+ if (g_weDistributeQueries) {
+ g_log<<Logger::Warning<<"Launching "<< g_numDistributorThreads <<" distributor threads"<<endl;
+ for(unsigned int n=0; n < g_numDistributorThreads; ++n) {
+ auto& infos = s_threadInfos.at(currentThreadId);
+ infos.thread = std::thread(recursorThread, currentThreadId++, "distr");
+ setCPUMap(cpusMap, currentThreadId, infos.thread.native_handle());
+ }
+ }
+
+ g_log<<Logger::Warning<<"Launching "<< g_numWorkerThreads <<" worker threads"<<endl;
+
+ for(unsigned int n=0; n < g_numWorkerThreads; ++n) {
+ auto& infos = s_threadInfos.at(currentThreadId);
+ infos.thread = std::thread(recursorThread, currentThreadId++, "worker");
+ setCPUMap(cpusMap, currentThreadId, infos.thread.native_handle());
+ }
+
+#ifdef HAVE_SYSTEMD
+ sd_notify(0, "READY=1");
+#endif
+
+ /* This thread handles the web server, carbon, statistics and the control channel */
+ auto& infos = s_threadInfos.at(0);
+ infos.isHandler = true;
+ infos.thread = std::thread(recursorThread, 0, "web+stat");
+
+ for (auto & ti : s_threadInfos) {
+ ti.thread.join();
+ if (ti.exitCode != 0) {
+ ret = ti.exitCode;
+ }
+ }
+ }
+
+ return ret;
+}
+
+static void handlePipeRequest(int fd, FDMultiplexer::funcparam_t& var)
+{
+ ThreadMSG* tmsg = nullptr;
+
+ if(read(fd, &tmsg, sizeof(tmsg)) != sizeof(tmsg)) { // fd == readToThread || fd == readQueriesToThread
+ unixDie("read from thread pipe returned wrong size or error");
+ }
+
+ void *resp=0;
+ try {
+ resp = tmsg->func();
+ }
+ catch(std::exception& e) {
+ if(g_logCommonErrors)
+ g_log<<Logger::Error<<"PIPE function we executed created exception: "<<e.what()<<endl; // but what if they wanted an answer.. we send 0
+ }
+ catch(PDNSException& e) {
+ if(g_logCommonErrors)
+ g_log<<Logger::Error<<"PIPE function we executed created PDNS exception: "<<e.reason<<endl; // but what if they wanted an answer.. we send 0
+ }
+ if(tmsg->wantAnswer) {
+ const auto& threadInfo = s_threadInfos.at(t_id);
+ if(write(threadInfo.pipes.writeFromThread, &resp, sizeof(resp)) != sizeof(resp)) {
+ delete tmsg;
+ unixDie("write to thread pipe returned wrong size or error");
+ }
+ }
+
+ delete tmsg;
+}
+
+static void handleRCC(int fd, FDMultiplexer::funcparam_t& var)
+{
+ try {
+ FDWrapper clientfd = accept(fd, nullptr, nullptr);
+ if (clientfd == -1) {
+ throw PDNSException("accept failed");
+ }
+ string msg = s_rcc.recv(clientfd).d_str;
+ g_log << Logger::Info << "Received rec_control command '" << msg << "' via controlsocket" << endl;
+
+ RecursorControlParser rcp;
+ RecursorControlParser::func_t* command;
+ auto answer = rcp.getAnswer(clientfd, msg, &command);
+
+ s_rcc.send(clientfd, answer);
+ command();
+ }
+ catch(const std::exception& e) {
+ g_log<<Logger::Error<<"Error dealing with control socket request: "<<e.what()<<endl;
+ }
+ catch(const PDNSException& ae) {
+ g_log<<Logger::Error<<"Error dealing with control socket request: "<<ae.reason<<endl;
+ }
+}
+static void houseKeeping(void *)
+{
+ static thread_local time_t last_rootupdate, last_secpoll, last_trustAnchorUpdate{0};
+ static thread_local struct timeval last_prune;
+
+ static thread_local int cleanCounter=0;
+ static thread_local bool s_running; // houseKeeping can get suspended in secpoll, and be restarted, which makes us do duplicate work
+ static time_t last_RC_prune = 0;
+ auto luaconfsLocal = g_luaconfs.getLocal();
+
+ if (last_trustAnchorUpdate == 0 && !luaconfsLocal->trustAnchorFileInfo.fname.empty() && luaconfsLocal->trustAnchorFileInfo.interval != 0) {
+ // Loading the Lua config file already "refreshed" the TAs
+ last_trustAnchorUpdate = g_now.tv_sec + luaconfsLocal->trustAnchorFileInfo.interval * 3600;
+ }
+
+ try {
+ if(s_running) {
+ return;
+ }
+ s_running=true;
+
+ runTaskOnce(g_logCommonErrors);
+
+ struct timeval now, past;
+ Utility::gettimeofday(&now, nullptr);
+ past = now;
+ past.tv_sec -= 5;
+ if (last_prune < past) {
+ t_packetCache->doPruneTo(g_maxPacketCacheEntries / (g_numWorkerThreads + g_numDistributorThreads));
+
+ time_t limit;
+ if(!((cleanCounter++)%40)) { // this is a full scan!
+ limit=now.tv_sec-300;
+ SyncRes::pruneNSSpeeds(limit);
+ }
+ limit = now.tv_sec - SyncRes::s_serverdownthrottletime * 10;
+ SyncRes::pruneFailedServers(limit);
+ limit = now.tv_sec - 2*3600;
+ SyncRes::pruneEDNSStatuses(limit);
+ SyncRes::pruneThrottledServers();
+ SyncRes::pruneNonResolving(now.tv_sec - SyncRes::s_nonresolvingnsthrottletime);
+ Utility::gettimeofday(&last_prune, nullptr);
+ t_tcp_manager.cleanup(now);
+ }
+
+ if(isHandlerThread()) {
+ if (now.tv_sec - last_RC_prune > 5) {
+ g_recCache->doPrune(g_maxCacheEntries);
+ g_negCache->prune(g_maxCacheEntries / 10);
+ if (g_aggressiveNSECCache) {
+ g_aggressiveNSECCache->prune(now.tv_sec);
+ }
+ last_RC_prune = now.tv_sec;
+ }
+ // Divide by 12 to get the original 2 hour cycle if s_maxcachettl is default (1 day)
+ if (now.tv_sec - last_rootupdate > max(SyncRes::s_maxcachettl / 12, 10U)) {
+ int res = SyncRes::getRootNS(g_now, nullptr, 0);
+ if (!res) {
+ last_rootupdate=now.tv_sec;
+ try {
+ primeRootNSZones(g_dnssecmode != DNSSECMode::Off, 0);
+ }
+ catch (const std::exception& e) {
+ g_log<<Logger::Error<<"Exception while priming the root NS zones: "<<e.what()<<endl;
+ }
+ catch (const PDNSException& e) {
+ g_log<<Logger::Error<<"Exception while priming the root NS zones: "<<e.reason<<endl;
+ }
+ catch (const ImmediateServFailException& e) {
+ g_log<<Logger::Error<<"Exception while priming the root NS zones: "<<e.reason<<endl;
+ }
+ catch (const PolicyHitException& e) {
+ g_log<<Logger::Error<<"Policy hit while priming the root NS zones"<<endl;
+ }
+ catch (...)
+ {
+ g_log<<Logger::Error<<"Exception while priming the root NS zones"<<endl;
+ }
+ }
+ }
+
+ if(now.tv_sec - last_secpoll >= 3600) {
+ try {
+ doSecPoll(&last_secpoll);
+ }
+ catch (const std::exception& e)
+ {
+ g_log<<Logger::Error<<"Exception while performing security poll: "<<e.what()<<endl;
+ }
+ catch (const PDNSException& e)
+ {
+ g_log<<Logger::Error<<"Exception while performing security poll: "<<e.reason<<endl;
+ }
+ catch (const ImmediateServFailException &e)
+ {
+ g_log<<Logger::Error<<"Exception while performing security poll: "<<e.reason<<endl;
+ }
+ catch (const PolicyHitException& e) {
+ g_log<<Logger::Error<<"Policy hit while performing security poll"<<endl;
+ }
+ catch (...)
+ {
+ g_log<<Logger::Error<<"Exception while performing security poll"<<endl;
+ }
+ }
+
+ if (!luaconfsLocal->trustAnchorFileInfo.fname.empty() && luaconfsLocal->trustAnchorFileInfo.interval != 0 &&
+ g_now.tv_sec - last_trustAnchorUpdate >= (luaconfsLocal->trustAnchorFileInfo.interval * 3600)) {
+ g_log<<Logger::Debug<<"Refreshing Trust Anchors from file"<<endl;
+ try {
+ map<DNSName, dsmap_t> dsAnchors;
+ if (updateTrustAnchorsFromFile(luaconfsLocal->trustAnchorFileInfo.fname, dsAnchors)) {
+ g_luaconfs.modify([&dsAnchors](LuaConfigItems& lci) {
+ lci.dsAnchors = dsAnchors;
+ });
+ }
+ last_trustAnchorUpdate = now.tv_sec;
+ } catch (const PDNSException &pe) {
+ g_log<<Logger::Error<<"Unable to update Trust Anchors: "<<pe.reason<<endl;
+ }
+ }
+ }
+ s_running = false;
+ }
+ catch (const PDNSException& ae)
+ {
+ s_running = false;
+ g_log<<Logger::Error<<"Fatal error in housekeeping thread: "<<ae.reason<<endl;
+ throw;
+ }
+ catch (...)
+ {
+ s_running = false;
+ g_log<<Logger::Error<<"Uncaught exception in housekeeping thread"<<endl;
+ throw;
+ }
+}
+
+
+void* recursorThread(unsigned int n, const string& threadName)
+try
+{
+ t_id=n;
+ auto& threadInfo = s_threadInfos.at(t_id);
+
+ static string threadPrefix = "pdns-r/";
+ setThreadName(threadPrefix + threadName);
+
+ SyncRes tmp(g_now); // make sure it allocates tsstorage before we do anything, like primeHints or so..
+ SyncRes::setDomainMap(g_initialDomainMap);
+ t_allowFrom = g_initialAllowFrom;
+ t_allowNotifyFrom = g_initialAllowNotifyFrom;
+ t_allowNotifyFor = g_initialAllowNotifyFor;
+ t_udpclientsocks = std::make_unique<UDPClientSocks>();
+ t_tcpClientCounts = std::make_unique<tcpClientCounts_t>();
+
+ if (threadInfo.isHandler) {
+ if (!primeHints()) {
+ threadInfo.exitCode = EXIT_FAILURE;
+ RecursorControlChannel::stop = 1;
+ g_log<<Logger::Critical<<"Priming cache failed, stopping"<<endl;
+ return nullptr;
+ }
+ g_log<<Logger::Warning<<"Done priming cache with root hints"<<endl;
+ }
+
+ t_packetCache = std::make_unique<RecursorPacketCache>();
+
+#ifdef NOD_ENABLED
+ if (threadInfo.isWorker)
+ setupNODThread();
+#endif /* NOD_ENABLED */
+
+ /* the listener threads handle TCP queries */
+ if(threadInfo.isWorker || threadInfo.isListener) {
+ try {
+ if(!::arg()["lua-dns-script"].empty()) {
+ t_pdl = std::make_shared<RecursorLua4>();
+ t_pdl->loadFile(::arg()["lua-dns-script"]);
+ g_log<<Logger::Warning<<"Loaded 'lua' script from '"<<::arg()["lua-dns-script"]<<"'"<<endl;
+ }
+ }
+ catch(std::exception &e) {
+ g_log<<Logger::Error<<"Failed to load 'lua' script from '"<<::arg()["lua-dns-script"]<<"': "<<e.what()<<endl;
+ _exit(99);
+ }
+ }
+
+ unsigned int ringsize=::arg().asNum("stats-ringbuffer-entries") / g_numWorkerThreads;
+ if(ringsize) {
+ t_remotes = std::make_unique<addrringbuf_t>();
+ if(g_weDistributeQueries)
+ t_remotes->set_capacity(::arg().asNum("stats-ringbuffer-entries") / g_numDistributorThreads);
+ else
+ t_remotes->set_capacity(ringsize);
+ t_servfailremotes = std::make_unique<addrringbuf_t>();
+ t_servfailremotes->set_capacity(ringsize);
+ t_bogusremotes = std::make_unique<addrringbuf_t>();
+ t_bogusremotes->set_capacity(ringsize);
+ t_largeanswerremotes = std::make_unique<addrringbuf_t>();
+ t_largeanswerremotes->set_capacity(ringsize);
+ t_timeouts = std::make_unique<addrringbuf_t>();
+ t_timeouts->set_capacity(ringsize);
+
+ t_queryring = std::make_unique<boost::circular_buffer<pair<DNSName, uint16_t>>>();
+ t_queryring->set_capacity(ringsize);
+ t_servfailqueryring = std::make_unique<boost::circular_buffer<pair<DNSName, uint16_t>>>();
+ t_servfailqueryring->set_capacity(ringsize);
+ t_bogusqueryring = std::make_unique<boost::circular_buffer<pair<DNSName, uint16_t>>>();
+ t_bogusqueryring->set_capacity(ringsize);
+ }
+ MT = std::make_unique<MT_t>(::arg().asNum("stack-size"));
+ threadInfo.mt = MT.get();
+
+ /* start protobuf export threads if needed */
+ auto luaconfsLocal = g_luaconfs.getLocal();
+ checkProtobufExport(luaconfsLocal);
+ checkOutgoingProtobufExport(luaconfsLocal);
+#ifdef HAVE_FSTRM
+ checkFrameStreamExport(luaconfsLocal);
+#endif
+
+ PacketID pident;
+
+ t_fdm=getMultiplexer();
+
+ RecursorWebServer *rws = nullptr;
+
+ t_fdm->addReadFD(threadInfo.pipes.readToThread, handlePipeRequest);
+
+ if(threadInfo.isHandler) {
+ if(::arg().mustDo("webserver")) {
+ g_log<<Logger::Warning << "Enabling web server" << endl;
+ try {
+ rws = new RecursorWebServer(t_fdm);
+ }
+ catch (const PDNSException &e) {
+ g_log<<Logger::Error<<"Unable to start the internal web server: "<<e.reason<<endl;
+ _exit(99);
+ }
+ }
+ g_log<<Logger::Info<<"Enabled '"<< t_fdm->getName() << "' multiplexer"<<endl;
+ }
+ else {
+ t_fdm->addReadFD(threadInfo.pipes.readQueriesToThread, handlePipeRequest);
+
+ if (threadInfo.isListener) {
+ if (g_reusePort) {
+ /* then every listener has its own FDs */
+ for(const auto& deferred : threadInfo.deferredAdds) {
+ t_fdm->addReadFD(deferred.first, deferred.second);
+ }
+ }
+ else {
+ /* otherwise all listeners are listening on the same ones */
+ for(const auto& deferred : g_deferredAdds) {
+ t_fdm->addReadFD(deferred.first, deferred.second);
+ }
+ }
+ }
+ }
+
+ registerAllStats();
+
+ if(threadInfo.isHandler) {
+ t_fdm->addReadFD(s_rcc.d_fd, handleRCC); // control channel
+ }
+
+ unsigned int maxTcpClients=::arg().asNum("max-tcp-clients");
+
+ bool listenOnTCP(true);
+
+ time_t last_stat = 0;
+ time_t last_carbon=0, last_lua_maintenance=0;
+ time_t carbonInterval=::arg().asNum("carbon-interval");
+ time_t luaMaintenanceInterval=::arg().asNum("lua-maintenance-interval");
+ counter.store(0); // used to periodically execute certain tasks
+
+ while (!RecursorControlChannel::stop) {
+ while(MT->schedule(&g_now)); // MTasker letting the mthreads do their thing
+
+ // Use primes, it avoid not being scheduled in cases where the counter has a regular pattern.
+ // We want to call handler thread often, it gets scheduled about 2 times per second
+ if ((threadInfo.isHandler && counter % 11 == 0) || counter % 499 == 0) {
+ MT->makeThread(houseKeeping, 0);
+ }
+
+ if(!(counter%55)) {
+ typedef vector<pair<int, FDMultiplexer::funcparam_t> > expired_t;
+ expired_t expired=t_fdm->getTimeouts(g_now);
+
+ for(expired_t::iterator i=expired.begin() ; i != expired.end(); ++i) {
+ shared_ptr<TCPConnection> conn=boost::any_cast<shared_ptr<TCPConnection> >(i->second);
+ if(g_logCommonErrors)
+ g_log<<Logger::Warning<<"Timeout from remote TCP client "<< conn->d_remote.toStringWithPort() <<endl;
+ t_fdm->removeReadFD(i->first);
+ }
+ }
+
+ counter++;
+
+ if(threadInfo.isHandler) {
+ if(statsWanted || (g_statisticsInterval > 0 && (g_now.tv_sec - last_stat) >= g_statisticsInterval)) {
+ doStats();
+ last_stat = g_now.tv_sec;
+ }
+
+ Utility::gettimeofday(&g_now, nullptr);
+
+ if((g_now.tv_sec - last_carbon) >= carbonInterval) {
+ MT->makeThread(doCarbonDump, 0);
+ last_carbon = g_now.tv_sec;
+ }
+ }
+ if (t_pdl != nullptr) {
+ // lua-dns-script directive is present, call the maintenance callback if needed
+ /* remember that the listener threads handle TCP queries */
+ if (threadInfo.isWorker || threadInfo.isListener) {
+ // Only on threads processing queries
+ if(g_now.tv_sec - last_lua_maintenance >= luaMaintenanceInterval) {
+ t_pdl->maintenance();
+ last_lua_maintenance = g_now.tv_sec;
+ }
+ }
+ }
+
+ t_fdm->run(&g_now);
+ // 'run' updates g_now for us
+
+ if(threadInfo.isListener) {
+ if(listenOnTCP) {
+ if(TCPConnection::getCurrentConnections() > maxTcpClients) { // shutdown, too many connections
+ for(const auto fd : threadInfo.tcpSockets) {
+ t_fdm->removeReadFD(fd);
+ }
+ listenOnTCP=false;
+ }
+ }
+ else {
+ if(TCPConnection::getCurrentConnections() <= maxTcpClients) { // reenable
+ for(const auto fd : threadInfo.tcpSockets) {
+ t_fdm->addReadFD(fd, handleNewTCPQuestion);
+ }
+ listenOnTCP=true;
+ }
+ }
+ }
+ }
+ delete rws;
+ delete t_fdm;
+ return 0;
+}
+catch(PDNSException &ae) {
+ g_log<<Logger::Error<<"Exception: "<<ae.reason<<endl;
+ return 0;
+}
+catch(std::exception &e) {
+ g_log<<Logger::Error<<"STL Exception: "<<e.what()<<endl;
+ return 0;
+}
+catch(...) {
+ g_log<<Logger::Error<<"any other exception in main: "<<endl;
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ g_argc = argc;
+ g_argv = argv;
+ g_stats.startupTime=time(0);
+ Utility::srandom();
+ versionSetProduct(ProductRecursor);
+ reportBasicTypes();
+ reportOtherTypes();
+
+ int ret = EXIT_SUCCESS;
+
+ try {
+#if HAVE_FIBER_SANITIZER
+ // Asan needs more stack
+ ::arg().set("stack-size","stack size per mthread")="400000";
+#else
+ ::arg().set("stack-size","stack size per mthread")="200000";
+#endif
+ ::arg().set("soa-minimum-ttl","Don't change")="0";
+ ::arg().set("no-shuffle","Don't change")="off";
+ ::arg().set("local-port","port to listen on")="53";
+ ::arg().set("local-address","IP addresses to listen on, separated by spaces or commas. Also accepts ports.")="127.0.0.1";
+ ::arg().setSwitch("non-local-bind", "Enable binding to non-local addresses by using FREEBIND / BINDANY socket options")="no";
+ ::arg().set("trace","if we should output heaps of logging. set to 'fail' to only log failing domains")="off";
+ ::arg().set("dnssec", "DNSSEC mode: off/process-no-validate/process (default)/log-fail/validate")="process";
+ ::arg().set("dnssec-log-bogus", "Log DNSSEC bogus validations")="no";
+ ::arg().set("signature-inception-skew", "Allow the signature inception to be off by this number of seconds")="60";
+ ::arg().set("daemon","Operate as a daemon")="no";
+ ::arg().setSwitch("write-pid","Write a PID file")="yes";
+ ::arg().set("loglevel","Amount of logging. Higher is more. Do not set below 3")="6";
+ ::arg().set("disable-syslog","Disable logging to syslog, useful when running inside a supervisor that logs stdout")="no";
+ ::arg().set("log-timestamp","Print timestamps in log lines, useful to disable when running with a tool that timestamps stdout already")="yes";
+ ::arg().set("log-common-errors","If we should log rather common errors")="no";
+ ::arg().set("chroot","switch to chroot jail")="";
+ ::arg().set("setgid","If set, change group id to this gid for more security"
+#ifdef HAVE_SYSTEMD
+#define SYSTEMD_SETID_MSG ". When running inside systemd, use the User and Group settings in the unit-file!"
+ SYSTEMD_SETID_MSG
+#endif
+ )="";
+ ::arg().set("setuid","If set, change user id to this uid for more security"
+#ifdef HAVE_SYSTEMD
+ SYSTEMD_SETID_MSG
+#endif
+ )="";
+ ::arg().set("network-timeout", "Wait this number of milliseconds for network i/o")="1500";
+ ::arg().set("threads", "Launch this number of threads")="2";
+ ::arg().set("distributor-threads", "Launch this number of distributor threads, distributing queries to other threads")="0";
+ ::arg().set("processes", "Launch this number of processes (EXPERIMENTAL, DO NOT CHANGE)")="1"; // if we un-experimental this, need to fix openssl rand seeding for multiple PIDs!
+ ::arg().set("config-name","Name of this virtual configuration - will rename the binary image")="";
+ ::arg().set("api-config-dir", "Directory where REST API stores config and zones") = "";
+ ::arg().set("api-key", "Static pre-shared authentication key for access to the REST API") = "";
+ ::arg().setSwitch("webserver", "Start a webserver (for REST API)") = "no";
+ ::arg().set("webserver-address", "IP Address of webserver to listen on") = "127.0.0.1";
+ ::arg().set("webserver-port", "Port of webserver to listen on") = "8082";
+ ::arg().set("webserver-password", "Password required for accessing the webserver") = "";
+ ::arg().set("webserver-allow-from","Webserver access is only allowed from these subnets")="127.0.0.1,::1";
+ ::arg().set("webserver-loglevel", "Amount of logging in the webserver (none, normal, detailed)") = "normal";
+ ::arg().setSwitch("webserver-hash-plaintext-credentials","Whether to hash passwords and api keys supplied in plaintext, to prevent keeping the plaintext version in memory at runtime")="no";
+ ::arg().set("carbon-ourname", "If set, overrides our reported hostname for carbon stats")="";
+ ::arg().set("carbon-server", "If set, send metrics in carbon (graphite) format to this server IP address")="";
+ ::arg().set("carbon-interval", "Number of seconds between carbon (graphite) updates")="30";
+ ::arg().set("carbon-namespace", "If set overwrites the first part of the carbon string")="pdns";
+ ::arg().set("carbon-instance", "If set overwrites the instance name default")="recursor";
+
+ ::arg().set("statistics-interval", "Number of seconds between printing of recursor statistics, 0 to disable")="1800";
+ ::arg().set("quiet","Suppress logging of questions and answers")="";
+ ::arg().set("logging-facility","Facility to log messages as. 0 corresponds to local0")="";
+ ::arg().set("config-dir","Location of configuration directory (recursor.conf)")=SYSCONFDIR;
+ ::arg().set("socket-owner","Owner of socket")="";
+ ::arg().set("socket-group","Group of socket")="";
+ ::arg().set("socket-mode", "Permissions for socket")="";
+
+ ::arg().set("socket-dir",string("Where the controlsocket will live, ")+LOCALSTATEDIR+"/pdns-recursor when unset and not chrooted"
+#ifdef HAVE_SYSTEMD
+ + ". Set to the RUNTIME_DIRECTORY environment variable when that variable has a value (e.g. under systemd).")="";
+ auto runtimeDir = getenv("RUNTIME_DIRECTORY");
+ if (runtimeDir != nullptr) {
+ ::arg().set("socket-dir") = runtimeDir;
+ }
+#else
+ )="";
+#endif
+ ::arg().set("query-local-address","Source IP address for sending queries")="0.0.0.0";
+ ::arg().set("client-tcp-timeout","Timeout in seconds when talking to TCP clients")="2";
+ ::arg().set("max-mthreads", "Maximum number of simultaneous Mtasker threads")="2048";
+ ::arg().set("max-tcp-clients","Maximum number of simultaneous TCP clients")="128";
+ ::arg().set("max-concurrent-requests-per-tcp-connection", "Maximum number of requests handled concurrently per TCP connection") = "10";
+ ::arg().set("server-down-max-fails","Maximum number of consecutive timeouts (and unreachables) to mark a server as down ( 0 => disabled )")="64";
+ ::arg().set("server-down-throttle-time","Number of seconds to throttle all queries to a server after being marked as down")="60";
+ ::arg().set("dont-throttle-names", "Do not throttle nameservers with this name or suffix")="";
+ ::arg().set("dont-throttle-netmasks", "Do not throttle nameservers with this IP netmask")="";
+ ::arg().set("non-resolving-ns-max-fails", "Number of failed address resolves of a nameserver to start throttling it, 0 is disabled")="5";
+ ::arg().set("non-resolving-ns-throttle-time", "Number of seconds to throttle a nameserver with a name failing to resolve")="60";
+
+ ::arg().set("hint-file", "If set, load root hints from this file")="";
+ ::arg().set("max-cache-entries", "If set, maximum number of entries in the main cache")="1000000";
+ ::arg().set("max-negative-ttl", "maximum number of seconds to keep a negative cached entry in memory")="3600";
+ ::arg().set("max-cache-bogus-ttl", "maximum number of seconds to keep a Bogus (positive or negative) cached entry in memory")="3600";
+ ::arg().set("max-cache-ttl", "maximum number of seconds to keep a cached entry in memory")="86400";
+ ::arg().set("packetcache-ttl", "maximum number of seconds to keep a cached entry in packetcache")="3600";
+ ::arg().set("max-packetcache-entries", "maximum number of entries to keep in the packetcache")="500000";
+ ::arg().set("packetcache-servfail-ttl", "maximum number of seconds to keep a cached servfail entry in packetcache")="60";
+ ::arg().set("server-id", "Returned when queried for 'id.server' TXT or NSID, defaults to hostname, set custom or 'disabled'")="";
+ ::arg().set("stats-ringbuffer-entries", "maximum number of packets to store statistics for")="10000";
+ ::arg().set("version-string", "string reported on version.pdns or version.bind")=fullVersionString();
+ ::arg().set("allow-from", "If set, only allow these comma separated netmasks to recurse")=LOCAL_NETS;
+ ::arg().set("allow-from-file", "If set, load allowed netmasks from this file")="";
+ ::arg().set("allow-notify-for", "If set, NOTIFY requests for these zones will be allowed")="";
+ ::arg().set("allow-notify-for-file", "If set, load NOTIFY-allowed zones from this file")="";
+ ::arg().set("allow-notify-from", "If set, NOTIFY requests from these comma separated netmasks will be allowed")="";
+ ::arg().set("allow-notify-from-file", "If set, load NOTIFY-allowed netmasks from this file")="";
+ ::arg().set("entropy-source", "If set, read entropy from this file")="/dev/urandom";
+ ::arg().set("dont-query", "If set, do not query these netmasks for DNS data")=DONT_QUERY;
+ ::arg().set("max-tcp-per-client", "If set, maximum number of TCP sessions per client (IP address)")="0";
+ ::arg().set("max-tcp-queries-per-connection", "If set, maximum number of TCP queries in a TCP connection")="0";
+ ::arg().set("spoof-nearmiss-max", "If non-zero, assume spoofing after this many near misses")="1";
+ ::arg().set("single-socket", "If set, only use a single socket for outgoing queries")="off";
+ ::arg().set("auth-zones", "Zones for which we have authoritative data, comma separated domain=file pairs ")="";
+ ::arg().set("lua-config-file", "More powerful configuration options")="";
+ ::arg().setSwitch("allow-trust-anchor-query", "Allow queries for trustanchor.server CH TXT and negativetrustanchor.server CH TXT")="no";
+
+ ::arg().set("forward-zones", "Zones for which we forward queries, comma separated domain=ip pairs")="";
+ ::arg().set("forward-zones-recurse", "Zones for which we forward queries with recursion bit, comma separated domain=ip pairs")="";
+ ::arg().set("forward-zones-file", "File with (+)domain=ip pairs for forwarding")="";
+ ::arg().set("export-etc-hosts", "If we should serve up contents from /etc/hosts")="off";
+ ::arg().set("export-etc-hosts-search-suffix", "Also serve up the contents of /etc/hosts with this suffix")="";
+ ::arg().set("etc-hosts-file", "Path to 'hosts' file")="/etc/hosts";
+ ::arg().set("serve-rfc1918", "If we should be authoritative for RFC 1918 private IP space")="yes";
+ ::arg().set("lua-dns-script", "Filename containing an optional 'lua' script that will be used to modify dns answers")="";
+ ::arg().set("lua-maintenance-interval", "Number of seconds between calls to the lua user defined maintenance() function")="1";
+ ::arg().set("latency-statistic-size","Number of latency values to calculate the qa-latency average")="10000";
+ ::arg().setSwitch( "disable-packetcache", "Disable packetcache" )= "no";
+ ::arg().set("ecs-ipv4-bits", "Number of bits of IPv4 address to pass for EDNS Client Subnet")="24";
+ ::arg().set("ecs-ipv4-cache-bits", "Maximum number of bits of IPv4 mask to cache ECS response")="24";
+ ::arg().set("ecs-ipv6-bits", "Number of bits of IPv6 address to pass for EDNS Client Subnet")="56";
+ ::arg().set("ecs-ipv6-cache-bits", "Maximum number of bits of IPv6 mask to cache ECS response")="56";
+ ::arg().setSwitch("ecs-ipv4-never-cache", "If we should never cache IPv4 ECS responses")="no";
+ ::arg().setSwitch("ecs-ipv6-never-cache", "If we should never cache IPv6 ECS responses")="no";
+ ::arg().set("ecs-minimum-ttl-override", "The minimum TTL for records in ECS-specific answers")="1";
+ ::arg().set("ecs-cache-limit-ttl", "Minimum TTL to cache ECS response")="0";
+ ::arg().set("edns-subnet-whitelist", "List of netmasks and domains that we should enable EDNS subnet for (deprecated)")="";
+ ::arg().set("edns-subnet-allow-list", "List of netmasks and domains that we should enable EDNS subnet for")="";
+ ::arg().set("ecs-add-for", "List of client netmasks for which EDNS Client Subnet will be added")="0.0.0.0/0, ::/0, " LOCAL_NETS_INVERSE;
+ ::arg().set("ecs-scope-zero-address", "Address to send to allow-listed authoritative servers for incoming queries with ECS prefix-length source of 0")="";
+ ::arg().setSwitch( "use-incoming-edns-subnet", "Pass along received EDNS Client Subnet information")="no";
+ ::arg().setSwitch( "pdns-distributes-queries", "If PowerDNS itself should distribute queries over threads")="yes";
+ ::arg().setSwitch( "root-nx-trust", "If set, believe that an NXDOMAIN from the root means the TLD does not exist")="yes";
+ ::arg().setSwitch( "any-to-tcp","Answer ANY queries with tc=1, shunting to TCP" )="no";
+ ::arg().setSwitch( "lowercase-outgoing","Force outgoing questions to lowercase")="no";
+ ::arg().setSwitch("gettag-needs-edns-options", "If EDNS Options should be extracted before calling the gettag() hook")="no";
+ ::arg().set("udp-truncation-threshold", "Maximum UDP response size before we truncate")="1232";
+ ::arg().set("edns-outgoing-bufsize", "Outgoing EDNS buffer size")="1232";
+ ::arg().set("minimum-ttl-override", "The minimum TTL")="1";
+ ::arg().set("max-qperq", "Maximum outgoing queries per query")="60";
+ ::arg().set("max-ns-address-qperq", "Maximum outgoing NS address queries per query")="10";
+ ::arg().set("max-total-msec", "Maximum total wall-clock time per query in milliseconds, 0 for unlimited")="7000";
+ ::arg().set("max-recursion-depth", "Maximum number of internal recursion calls per query, 0 for unlimited")="40";
+ ::arg().set("max-udp-queries-per-round", "Maximum number of UDP queries processed per recvmsg() round, before returning back to normal processing")="10000";
+ ::arg().set("protobuf-use-kernel-timestamp", "Compute the latency of queries in protobuf messages by using the timestamp set by the kernel when the query was received (when available)")="";
+ ::arg().set("distribution-pipe-buffer-size", "Size in bytes of the internal buffer of the pipe used by the distributor to pass incoming queries to a worker thread")="0";
+
+ ::arg().set("include-dir","Include *.conf files from this directory")="";
+ ::arg().set("security-poll-suffix","Domain name from which to query security update notifications")="secpoll.powerdns.com.";
+
+ ::arg().setSwitch("reuseport","Enable SO_REUSEPORT allowing multiple recursors processes to listen to 1 address")="no";
+
+ ::arg().setSwitch("snmp-agent", "If set, register as an SNMP agent")="no";
+ ::arg().set("snmp-master-socket", "If set and snmp-agent is set, the socket to use to register to the SNMP daemon (deprecated)")="";
+ ::arg().set("snmp-daemon-socket", "If set and snmp-agent is set, the socket to use to register to the SNMP daemon")="";
+
+ std::string defaultAPIDisabledStats = "cache-bytes, packetcache-bytes, special-memory-usage";
+ for (size_t idx = 0; idx < 32; idx++) {
+ defaultAPIDisabledStats += ", ecs-v4-response-bits-" + std::to_string(idx + 1);
+ }
+ for (size_t idx = 0; idx < 128; idx++) {
+ defaultAPIDisabledStats += ", ecs-v6-response-bits-" + std::to_string(idx + 1);
+ }
+ std::string defaultDisabledStats = defaultAPIDisabledStats + ", cumul-clientanswers, cumul-authanswers, policy-hits";
+
+ ::arg().set("stats-api-blacklist", "List of statistics that are disabled when retrieving the complete list of statistics via the API (deprecated)")=defaultAPIDisabledStats;
+ ::arg().set("stats-carbon-blacklist", "List of statistics that are prevented from being exported via Carbon (deprecated)")=defaultDisabledStats;
+ ::arg().set("stats-rec-control-blacklist", "List of statistics that are prevented from being exported via rec_control get-all (deprecated)")=defaultDisabledStats;
+ ::arg().set("stats-snmp-blacklist", "List of statistics that are prevented from being exported via SNMP (deprecated)")=defaultDisabledStats;
+
+ ::arg().set("stats-api-disabled-list", "List of statistics that are disabled when retrieving the complete list of statistics via the API")=defaultAPIDisabledStats;
+ ::arg().set("stats-carbon-disabled-list", "List of statistics that are prevented from being exported via Carbon")=defaultDisabledStats;
+ ::arg().set("stats-rec-control-disabled-list", "List of statistics that are prevented from being exported via rec_control get-all")=defaultDisabledStats;
+ ::arg().set("stats-snmp-disabled-list", "List of statistics that are prevented from being exported via SNMP")=defaultDisabledStats;
+
+ ::arg().set("tcp-fast-open", "Enable TCP Fast Open support on the listening sockets, using the supplied numerical value as the queue size")="0";
+ ::arg().set("tcp-fast-open-connect", "Enable TCP Fast Open support on outgoing sockets")="no";
+ ::arg().set("nsec3-max-iterations", "Maximum number of iterations allowed for an NSEC3 record")="150";
+
+ ::arg().set("cpu-map", "Thread to CPU mapping, space separated thread-id=cpu1,cpu2..cpuN pairs")="";
+
+ ::arg().setSwitch("log-rpz-changes", "Log additions and removals to RPZ zones at Info level")="no";
+
+ ::arg().set("xpf-allow-from","XPF information is only processed from these subnets")="";
+ ::arg().set("xpf-rr-code","XPF option code to use")="0";
+
+ ::arg().set("proxy-protocol-from", "A Proxy Protocol header is only allowed from these subnets")="";
+ ::arg().set("proxy-protocol-maximum-size", "The maximum size of a proxy protocol payload, including the TLV values")="512";
+
+ ::arg().set("dns64-prefix", "DNS64 prefix")="";
+
+ ::arg().set("udp-source-port-min", "Minimum UDP port to bind on")="1024";
+ ::arg().set("udp-source-port-max", "Maximum UDP port to bind on")="65535";
+ ::arg().set("udp-source-port-avoid", "List of comma separated UDP port number to avoid")="11211";
+ ::arg().set("rng", "Specify random number generator to use. Valid values are auto,sodium,openssl,getrandom,arc4random,urandom.")="auto";
+ ::arg().set("public-suffix-list-file", "Path to the Public Suffix List file, if any")="";
+ ::arg().set("distribution-load-factor", "The load factor used when PowerDNS is distributing queries to worker threads")="0.0";
+
+ ::arg().setSwitch("qname-minimization", "Use Query Name Minimization")="yes";
+ ::arg().setSwitch("nothing-below-nxdomain", "When an NXDOMAIN exists in cache for a name with fewer labels than the qname, send NXDOMAIN without doing a lookup (see RFC 8020)")="dnssec";
+ ::arg().set("max-generate-steps", "Maximum number of $GENERATE steps when loading a zone from a file")="0";
+ ::arg().set("max-include-depth", "Maximum nested $INCLUDE depth when loading a zone from a file")="20";
+ ::arg().set("record-cache-shards", "Number of shards in the record cache")="1024";
+ ::arg().set("refresh-on-ttl-perc", "If a record is requested from the cache and only this % of original TTL remains, refetch") = "0";
+
+ ::arg().set("x-dnssec-names", "Collect DNSSEC statistics for names or suffixes in this list in separate x-dnssec counters")="";
+
+#ifdef NOD_ENABLED
+ ::arg().set("new-domain-tracking", "Track newly observed domains (i.e. never seen before).")="no";
+ ::arg().set("new-domain-log", "Log newly observed domains.")="yes";
+ ::arg().set("new-domain-lookup", "Perform a DNS lookup newly observed domains as a subdomain of the configured domain")="";
+ ::arg().set("new-domain-history-dir", "Persist new domain tracking data here to persist between restarts")=string(NODCACHEDIR)+"/nod";
+ ::arg().set("new-domain-whitelist", "List of domains (and implicitly all subdomains) which will never be considered a new domain (deprecated)")="";
+ ::arg().set("new-domain-ignore-list", "List of domains (and implicitly all subdomains) which will never be considered a new domain")="";
+ ::arg().set("new-domain-db-size", "Size of the DB used to track new domains in terms of number of cells. Defaults to 67108864")="67108864";
+ ::arg().set("new-domain-pb-tag", "If protobuf is configured, the tag to use for messages containing newly observed domains. Defaults to 'pdns-nod'")="pdns-nod";
+ ::arg().set("unique-response-tracking", "Track unique responses (tuple of query name, type and RR).")="no";
+ ::arg().set("unique-response-log", "Log unique responses")="yes";
+ ::arg().set("unique-response-history-dir", "Persist unique response tracking data here to persist between restarts")=string(NODCACHEDIR)+"/udr";
+ ::arg().set("unique-response-db-size", "Size of the DB used to track unique responses in terms of number of cells. Defaults to 67108864")="67108864";
+ ::arg().set("unique-response-pb-tag", "If protobuf is configured, the tag to use for messages containing unique DNS responses. Defaults to 'pdns-udr'")="pdns-udr";
+#endif /* NOD_ENABLED */
+
+ ::arg().setSwitch("extended-resolution-errors", "If set, send an EDNS Extended Error extension on resolution failures, like DNSSEC validation errors")="no";
+
+ ::arg().set("aggressive-nsec-cache-size", "The number of records to cache in the aggressive cache. If set to a value greater than 0, and DNSSEC processing or validation is enabled, the recursor will cache NSEC and NSEC3 records to generate negative answers, as defined in rfc8198")="100000";
+
+ ::arg().set("edns-padding-from", "List of netmasks (proxy IP in case of XPF or proxy-protocol presence, client IP otherwise) for which EDNS padding will be enabled in responses, provided that 'edns-padding-mode' applies")="";
+ ::arg().set("edns-padding-mode", "Whether to add EDNS padding to all responses ('always') or only to responses for queries containing the EDNS padding option ('padded-queries-only', the default). In both modes, padding will only be added to responses for queries coming from `edns-padding-from`_ sources")="padded-queries-only";
+ ::arg().set("edns-padding-tag", "Packetcache tag associated to responses sent with EDNS padding, to prevent sending these to clients for which padding is not enabled.")="7830";
+
+ ::arg().setSwitch("dot-to-port-853", "Force DoT connection to target port 853 if DoT compiled in")="yes";
+ ::arg().set("dot-to-auth-names", "Use DoT to authoritative servers with these names or suffixes")="";
+ ::arg().set("event-trace-enabled", "If set, event traces are collected and send out via protobuf logging (1), logfile (2) or both(3)")="0";
+
+ ::arg().set("tcp-out-max-idle-ms", "Time TCP/DoT connections are left idle in milliseconds or 0 if no limit") = "10000";
+ ::arg().set("tcp-out-max-idle-per-auth", "Maximum number of idle TCP/DoT connections to a specific IP per thread, 0 means do not keep idle connections open") = "10";
+ ::arg().set("tcp-out-max-queries", "Maximum total number of queries per TCP/DoT connection, 0 means no limit") = "0";
+ ::arg().set("tcp-out-max-idle-per-thread", "Maximum number of idle TCP/DoT connections per thread") = "100";
+ ::arg().setSwitch("structured-logging", "Prefer structured logging") = "yes";
+
+ ::arg().setCmd("help","Provide a helpful message");
+ ::arg().setCmd("version","Print version string");
+ ::arg().setCmd("config","Output blank configuration");
+ ::arg().setDefaults();
+ g_log.toConsole(Logger::Info);
+ ::arg().laxParse(argc,argv); // do a lax parse
+
+ if(::arg().mustDo("version")) {
+ showProductVersion();
+ showBuildConfiguration();
+ exit(0);
+ }
+
+ string configname=::arg()["config-dir"]+"/recursor.conf";
+ if(::arg()["config-name"]!="") {
+ configname=::arg()["config-dir"]+"/recursor-"+::arg()["config-name"]+".conf";
+ s_programname+="-"+::arg()["config-name"];
+ }
+ cleanSlashes(configname);
+
+ if(!::arg().getCommands().empty()) {
+ cerr<<"Fatal: non-option";
+ if (::arg().getCommands().size() > 1) {
+ cerr<<"s";
+ }
+ cerr<<" (";
+ bool first = true;
+ for (const auto& c : ::arg().getCommands()) {
+ if (!first) {
+ cerr<<", ";
+ }
+ first = false;
+ cerr<<c;
+ }
+ cerr<<") on the command line, perhaps a '--setting=123' statement missed the '='?"<<endl;
+ exit(99);
+ }
+
+ if(::arg().mustDo("config")) {
+ cout<<::arg().configstring(false, true);
+ exit(0);
+ }
+
+ g_slog = Logging::Logger::create(loggerBackend);
+ auto startupLog = g_slog->withName("startup");
+
+ if(!::arg().file(configname.c_str())) {
+ SLOG(g_log<<Logger::Warning<<"Unable to parse configuration file '"<<configname<<"'"<<endl,
+ startupLog->error("No such file", "Unable to parse configuration file", "config_file", Logging::Loggable(configname)));
+ }
+
+ ::arg().parse(argc,argv);
+
+ if( !::arg()["chroot"].empty() && !::arg()["api-config-dir"].empty() ) {
+ SLOG(g_log<<Logger::Error<<"Using chroot and enabling the API is not possible"<<endl,
+ startupLog->info("Cannot use chroot and enable the API at the same time"));
+ exit(EXIT_FAILURE);
+ }
+
+ if (::arg()["socket-dir"].empty()) {
+ if (::arg()["chroot"].empty())
+ ::arg().set("socket-dir") = std::string(LOCALSTATEDIR) + "/pdns-recursor";
+ else
+ ::arg().set("socket-dir") = "/";
+ }
+
+ if(::arg().asNum("threads")==1) {
+ if (::arg().mustDo("pdns-distributes-queries")) {
+ SLOG(g_log<<Logger::Warning<<"Asked to run with pdns-distributes-queries set but no distributor threads, raising to 1"<<endl,
+ startupLog->v(1)->info("Only one thread, no need to distribute queries ourselves"));
+ ::arg().set("pdns-distributes-queries")="no";
+ }
+ }
+
+ if(::arg().mustDo("pdns-distributes-queries") && ::arg().asNum("distributor-threads") <= 0) {
+ SLOG(g_log<<Logger::Warning<<"Asked to run with pdns-distributes-queries set but no distributor threads, raising to 1"<<endl,
+ startupLog->v(1)->info("Asked to run with pdns-distributes-queries set but no distributor threads, raising to 1"));
+ ::arg().set("distributor-threads")="1";
+ }
+
+ if (!::arg().mustDo("pdns-distributes-queries")) {
+ ::arg().set("distributor-threads")="0";
+ }
+
+ if(::arg().mustDo("help")) {
+ cout<<"syntax:"<<endl<<endl;
+ cout<<::arg().helpstring(::arg()["help"])<<endl;
+ exit(0);
+ }
+ g_recCache = std::make_unique<MemRecursorCache>(::arg().asNum("record-cache-shards"));
+ g_negCache = std::make_unique<NegCache>(::arg().asNum("record-cache-shards"));
+
+ g_quiet=::arg().mustDo("quiet");
+ Logger::Urgency logUrgency = (Logger::Urgency)::arg().asNum("loglevel");
+
+ if (logUrgency < Logger::Error)
+ logUrgency = Logger::Error;
+ if(!g_quiet && logUrgency < Logger::Info) { // Logger::Info=6, Logger::Debug=7
+ logUrgency = Logger::Info; // if you do --quiet=no, you need Info to also see the query log
+ }
+ g_log.setLoglevel(logUrgency);
+ g_log.toConsole(logUrgency);
+
+ ret = serviceMain(argc, argv);
+ }
+ catch(PDNSException &ae) {
+ g_log<<Logger::Error<<"Exception: "<<ae.reason<<endl;
+ ret=EXIT_FAILURE;
+ }
+ catch(std::exception &e) {
+ g_log<<Logger::Error<<"STL Exception: "<<e.what()<<endl;
+ ret=EXIT_FAILURE;
+ }
+ catch(...) {
+ g_log<<Logger::Error<<"any other exception in main: "<<endl;
+ ret=EXIT_FAILURE;
+ }
+
+ return ret;
+}
+
+
+
+static RecursorControlChannel::Answer* doReloadLuaScript()
+{
+ string fname= ::arg()["lua-dns-script"];
+ try {
+ if(fname.empty()) {
+ t_pdl.reset();
+ g_log<<Logger::Info<<t_id<<" Unloaded current lua script"<<endl;
+ return new RecursorControlChannel::Answer{0, string("unloaded\n")};
+ }
+ else {
+ t_pdl = std::make_shared<RecursorLua4>();
+ int err = t_pdl->loadFile(fname);
+ if (err != 0) {
+ string msg = std::to_string(t_id) + " Retaining current script, could not read '" + fname + "': " + stringerror(err);
+ g_log<<Logger::Error<<msg<<endl;
+ return new RecursorControlChannel::Answer{1, msg + "\n"};
+ }
+ }
+ }
+ catch(std::exception& e) {
+ g_log<<Logger::Error<<t_id<<" Retaining current script, error from '"<<fname<<"': "<< e.what() <<endl;
+ return new RecursorControlChannel::Answer{1, string("retaining current script, error from '"+fname+"': "+e.what()+"\n")};
+ }
+
+ g_log<<Logger::Warning<<t_id<<" (Re)loaded lua script from '"<<fname<<"'"<<endl;
+ return new RecursorControlChannel::Answer{0, string("(re)loaded '"+fname+"'\n")};
+}
+
+RecursorControlChannel::Answer doQueueReloadLuaScript(vector<string>::const_iterator begin, vector<string>::const_iterator end)
+{
+ if(begin != end)
+ ::arg().set("lua-dns-script") = *begin;
+
+ return broadcastAccFunction<RecursorControlChannel::Answer>(doReloadLuaScript);
+}
+
+static string* pleaseUseNewTraceRegex(const std::string& newRegex)
+try
+{
+ if(newRegex.empty()) {
+ t_traceRegex.reset();
+ return new string("unset\n");
+ }
+ else {
+ t_traceRegex = std::make_shared<Regex>(newRegex);
+ return new string("ok\n");
+ }
+}
+catch(PDNSException& ae)
+{
+ return new string(ae.reason+"\n");
+}
+
+string doTraceRegex(vector<string>::const_iterator begin, vector<string>::const_iterator end)
+{
+ return broadcastAccFunction<string>([=]{ return pleaseUseNewTraceRegex(begin!=end ? *begin : ""); });
+}
+
+
+static uint64_t* pleaseWipePacketCache(const DNSName& canon, bool subtree, uint16_t qtype)
+{
+ return new uint64_t(t_packetCache->doWipePacketCache(canon, qtype, subtree));
+}
+
+struct WipeCacheResult wipeCaches(const DNSName& canon, bool subtree, uint16_t qtype)
+{
+ struct WipeCacheResult res;
+
+ try {
+ res.record_count = g_recCache->doWipeCache(canon, subtree, qtype);
+ res.packet_count = broadcastAccFunction<uint64_t>([=]{ return pleaseWipePacketCache(canon, subtree, qtype);});
+ res.negative_record_count = g_negCache->wipe(canon, subtree);
+ if (g_aggressiveNSECCache) {
+ g_aggressiveNSECCache->removeZoneInfo(canon, subtree);
+ }
+ }
+ catch (const std::exception& e) {
+ g_log<<Logger::Warning<<", failed: "<<e.what()<<endl;
+ }
+
+ return res;
+}