/* * This file is part of PowerDNS or dnsdist. * Copyright -- PowerDNS.COM B.V. and its contributors * * This program is free software; you can redistribute it and/or modify * it under the terms of version 2 of the GNU General Public License as * published by the Free Software Foundation. * * In addition, for the avoidance of any doubt, permission is granted to * link this program with OpenSSL and to (re)distribute the binaries * produced as the result of such linking. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include // for OpenBSD, sys/socket.h needs to come before net/if.h #include #include #include #include #include #include "dnsdist.hh" #include "dnsdist-console.hh" #include "dnsdist-dynblocks.hh" #include "dnsdist-ecs.hh" #include "dnsdist-healthchecks.hh" #include "dnsdist-lua.hh" #ifdef LUAJIT_VERSION #include "dnsdist-lua-ffi.hh" #endif /* LUAJIT_VERSION */ #include "dnsdist-nghttp2.hh" #include "dnsdist-proxy-protocol.hh" #include "dnsdist-rings.hh" #include "dnsdist-secpoll.hh" #include "dnsdist-session-cache.hh" #include "dnsdist-tcp-downstream.hh" #include "dnsdist-web.hh" #include "base64.hh" #include "dnswriter.hh" #include "dolog.hh" #include "lock.hh" #include "sodcrypto.hh" #ifdef HAVE_LIBSSL #include "libssl.hh" #endif #include #include #ifdef HAVE_SYSTEMD #include #endif using std::thread; static boost::optional>> g_launchWork = boost::none; boost::tribool g_noLuaSideEffect; static bool g_included{false}; /* this is a best effort way to prevent logging calls with no side-effects in the output of delta() Functions can declare setLuaNoSideEffect() and if nothing else does declare a side effect, or nothing has done so before on this invocation, this call won't be part of delta() output */ void setLuaNoSideEffect() { if (g_noLuaSideEffect == false) // there has been a side effect already return; g_noLuaSideEffect = true; } void setLuaSideEffect() { g_noLuaSideEffect = false; } bool getLuaNoSideEffect() { if (g_noLuaSideEffect) { return true; } return false; } void resetLuaSideEffect() { g_noLuaSideEffect = boost::logic::indeterminate; } typedef std::unordered_map>, std::vector>, std::map>> localbind_t; static void parseLocalBindVars(boost::optional vars, bool& reusePort, int& tcpFastOpenQueueSize, std::string& interface, std::set& cpus, int& tcpListenQueueSize, size_t& maxInFlightQueriesPerConnection, size_t& tcpMaxConcurrentConnections) { if (vars) { if (vars->count("reusePort")) { reusePort = boost::get((*vars)["reusePort"]); } if (vars->count("tcpFastOpenQueueSize")) { tcpFastOpenQueueSize = boost::get((*vars)["tcpFastOpenQueueSize"]); } if (vars->count("tcpListenQueueSize")) { tcpListenQueueSize = boost::get((*vars)["tcpListenQueueSize"]); } if (vars->count("maxConcurrentTCPConnections")) { tcpMaxConcurrentConnections = boost::get((*vars)["maxConcurrentTCPConnections"]); } if (vars->count("maxInFlight")) { maxInFlightQueriesPerConnection = boost::get((*vars)["maxInFlight"]); } if (vars->count("interface")) { interface = boost::get((*vars)["interface"]); } if (vars->count("cpus")) { for (const auto& cpu : boost::get>>((*vars)["cpus"])) { cpus.insert(cpu.second); } } } } #if defined(HAVE_DNS_OVER_TLS) || defined(HAVE_DNS_OVER_HTTPS) static bool loadTLSCertificateAndKeys(const std::string& context, std::vector>& pairs, boost::variant>> certFiles, boost::variant>> keyFiles) { if (certFiles.type() == typeid(std::string) && keyFiles.type() == typeid(std::string)) { auto certFile = boost::get(certFiles); auto keyFile = boost::get(keyFiles); pairs.clear(); pairs.emplace_back(certFile, keyFile); } else if (certFiles.type() == typeid(std::vector>) && keyFiles.type() == typeid(std::vector>)) { auto certFilesVect = boost::get>>(certFiles); auto keyFilesVect = boost::get>>(keyFiles); if (certFilesVect.size() == keyFilesVect.size()) { pairs.clear(); for (size_t idx = 0; idx < certFilesVect.size(); idx++) { pairs.emplace_back(certFilesVect.at(idx).second, keyFilesVect.at(idx).second); } } else { errlog("Error, mismatching number of certificates and keys in call to %s()!", context); g_outputBuffer = "Error, mismatching number of certificates and keys in call to " + context + "()!"; return false; } } else { errlog("Error, mismatching number of certificates and keys in call to %s()!", context); g_outputBuffer = "Error, mismatching number of certificates and keys in call to " + context + "()!"; return false; } return true; } static void parseTLSConfig(TLSConfig& config, const std::string& context, boost::optional vars) { if (vars->count("ciphers")) { config.d_ciphers = boost::get((*vars)["ciphers"]); } if (vars->count("ciphersTLS13")) { config.d_ciphers13 = boost::get((*vars)["ciphersTLS13"]); } #ifdef HAVE_LIBSSL if (vars->count("minTLSVersion")) { config.d_minTLSVersion = libssl_tls_version_from_string(boost::get((*vars)["minTLSVersion"])); } #endif /* HAVE_LIBSSL */ if (vars->count("ticketKeyFile")) { config.d_ticketKeyFile = boost::get((*vars)["ticketKeyFile"]); } if (vars->count("ticketsKeysRotationDelay")) { config.d_ticketsKeyRotationDelay = boost::get((*vars)["ticketsKeysRotationDelay"]); } if (vars->count("numberOfTicketsKeys")) { config.d_numberOfTicketsKeys = boost::get((*vars)["numberOfTicketsKeys"]); } if (vars->count("preferServerCiphers")) { config.d_preferServerCiphers = boost::get((*vars)["preferServerCiphers"]); } if (vars->count("sessionTimeout")) { config.d_sessionTimeout = boost::get((*vars)["sessionTimeout"]); } if (vars->count("sessionTickets")) { config.d_enableTickets = boost::get((*vars)["sessionTickets"]); } if (vars->count("numberOfStoredSessions")) { auto value = boost::get((*vars)["numberOfStoredSessions"]); if (value < 0) { errlog("Invalid value '%d' for %s() parameter 'numberOfStoredSessions', should be >= 0, dismissing", value, context); g_outputBuffer = "Invalid value '" + std::to_string(value) + "' for " + context + "() parameter 'numberOfStoredSessions', should be >= 0, dismissing"; } config.d_maxStoredSessions = value; } if (vars->count("ocspResponses")) { auto files = boost::get>>((*vars)["ocspResponses"]); for (const auto& file : files) { config.d_ocspFiles.push_back(file.second); } } if (vars->count("keyLogFile")) { #ifdef HAVE_SSL_CTX_SET_KEYLOG_CALLBACK config.d_keyLogFile = boost::get((*vars)["keyLogFile"]); #else errlog("TLS Key logging has been enabled using the 'keyLogFile' parameter to %s(), but this version of OpenSSL does not support it", context); g_outputBuffer = "TLS Key logging has been enabled using the 'keyLogFile' parameter to " + context + "(), but this version of OpenSSL does not support it"; #endif } if (vars->count("releaseBuffers")) { config.d_releaseBuffers = boost::get((*vars)["releaseBuffers"]); } if (vars->count("enableRenegotiation")) { config.d_enableRenegotiation = boost::get((*vars)["enableRenegotiation"]); } } #endif // defined(HAVE_DNS_OVER_TLS) || defined(HAVE_DNS_OVER_HTTPS) static void checkParameterBound(const std::string& parameter, uint64_t value, size_t max = std::numeric_limits::max()) { if (value > max) { throw std::runtime_error("The value (" + std::to_string(value) + ") passed to " + parameter + " is too large, the maximum is " + std::to_string(max)); } } static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck) { typedef std::unordered_map>, DownstreamState::checkfunc_t>> newserver_t; luaCtx.writeFunction("inClientStartup", [client]() { return client && !g_configurationDone; }); luaCtx.writeFunction("inConfigCheck", [configCheck]() { return configCheck; }); luaCtx.writeFunction("newServer", [client, configCheck](boost::variant pvars, boost::optional qps) { setLuaSideEffect(); newserver_t vars; ComboAddress serverAddr; std::string serverAddressStr; if (auto addrStr = boost::get(&pvars)) { serverAddressStr = *addrStr; if (qps) { vars["qps"] = std::to_string(*qps); } } else { vars = boost::get(pvars); serverAddressStr = boost::get(vars["address"]); } try { serverAddr = ComboAddress(serverAddressStr, 53); } catch (const PDNSException& e) { g_outputBuffer = "Error creating new server: " + string(e.reason); errlog("Error creating new server with address %s: %s", serverAddressStr, e.reason); return std::shared_ptr(); } catch (const std::exception& e) { g_outputBuffer = "Error creating new server: " + string(e.what()); errlog("Error creating new server with address %s: %s", serverAddressStr, e.what()); return std::shared_ptr(); } if (IsAnyAddress(serverAddr)) { g_outputBuffer = "Error creating new server: invalid address for a downstream server."; errlog("Error creating new server: %s is not a valid address for a downstream server", serverAddressStr); return std::shared_ptr(); } ComboAddress sourceAddr; std::string sourceItfName; unsigned int sourceItf = 0; size_t numberOfSockets = 1; std::set cpus; if (vars.count("source")) { /* handle source in the following forms: - v4 address ("192.0.2.1") - v6 address ("2001:DB8::1") - interface name ("eth0") - v4 address and interface name ("192.0.2.1@eth0") - v6 address and interface name ("2001:DB8::1@eth0") */ const string source = boost::get(vars["source"]); bool parsed = false; std::string::size_type pos = source.find("@"); if (pos == std::string::npos) { /* no '@', try to parse that as a valid v4/v6 address */ try { sourceAddr = ComboAddress(source); parsed = true; } catch (...) { } } if (parsed == false) { /* try to parse as interface name, or v4/v6@itf */ sourceItfName = source.substr(pos == std::string::npos ? 0 : pos + 1); unsigned int itfIdx = if_nametoindex(sourceItfName.c_str()); if (itfIdx != 0) { if (pos == 0 || pos == std::string::npos) { /* "eth0" or "@eth0" */ sourceItf = itfIdx; } else { /* "192.0.2.1@eth0" */ sourceAddr = ComboAddress(source.substr(0, pos)); sourceItf = itfIdx; } #ifdef SO_BINDTODEVICE /* we need to retain CAP_NET_RAW to be able to set SO_BINDTODEVICE in the health checks */ g_capabilitiesToRetain.insert("CAP_NET_RAW"); #endif } else { warnlog("Dismissing source %s because '%s' is not a valid interface name", source, sourceItfName); } } } if (vars.count("sockets")) { numberOfSockets = std::stoul(boost::get(vars["sockets"])); if (numberOfSockets == 0) { warnlog("Dismissing invalid number of sockets '%s', using 1 instead", boost::get(vars["sockets"])); numberOfSockets = 1; } } // create but don't connect the socket in client or check-config modes auto ret = std::make_shared(serverAddr, sourceAddr, sourceItf, sourceItfName); if (!(client || configCheck)) { infolog("Added downstream server %s", serverAddr.toStringWithPort()); } if (vars.count("qps")) { int qpsVal = std::stoi(boost::get(vars["qps"])); ret->qps = QPSLimiter(qpsVal, qpsVal); } if (vars.count("order")) { ret->order = std::stoi(boost::get(vars["order"])); } if (vars.count("weight")) { try { int weightVal = std::stoi(boost::get(vars["weight"])); if (weightVal < 1) { errlog("Error creating new server: downstream weight value must be greater than 0."); return ret; } ret->setWeight(weightVal); } catch (const std::exception& e) { // std::stoi will throw an exception if the string isn't in a value int range errlog("Error creating new server: downstream weight value must be between %s and %s", 1, std::numeric_limits::max()); return ret; } } if (vars.count("retries")) { ret->d_retries = std::stoi(boost::get(vars["retries"])); } if (vars.count("checkInterval")) { ret->checkInterval = static_cast(std::stoul(boost::get(vars["checkInterval"]))); } if (vars.count("tcpConnectTimeout")) { ret->tcpConnectTimeout = std::stoi(boost::get(vars["tcpConnectTimeout"])); } if (vars.count("tcpSendTimeout")) { ret->tcpSendTimeout = std::stoi(boost::get(vars["tcpSendTimeout"])); } if (vars.count("tcpRecvTimeout")) { ret->tcpRecvTimeout = std::stoi(boost::get(vars["tcpRecvTimeout"])); } if (vars.count("tcpFastOpen")) { bool fastOpen = boost::get(vars["tcpFastOpen"]); if (fastOpen) { #ifdef MSG_FASTOPEN ret->tcpFastOpen = true; #else warnlog("TCP Fast Open has been configured on downstream server %s but is not supported", boost::get(vars["address"])); #endif } } if (vars.count("maxInFlight")) { ret->d_maxInFlightQueriesPerConn = std::stoi(boost::get(vars["maxInFlight"])); } if (vars.count("name")) { ret->setName(boost::get(vars["name"])); } if (vars.count("id")) { ret->setId(boost::lexical_cast(boost::get(vars["id"]))); } if (vars.count("checkName")) { ret->checkName = DNSName(boost::get(vars["checkName"])); } if (vars.count("checkType")) { ret->checkType = boost::get(vars["checkType"]); } if (vars.count("checkClass")) { ret->checkClass = std::stoi(boost::get(vars["checkClass"])); } if (vars.count("checkFunction")) { ret->checkFunction = boost::get(vars["checkFunction"]); } if (vars.count("checkTimeout")) { ret->checkTimeout = std::stoi(boost::get(vars["checkTimeout"])); } if (vars.count("checkTCP")) { ret->d_tcpCheck = boost::get(vars.at("checkTCP")); } if (vars.count("setCD")) { ret->setCD = boost::get(vars["setCD"]); } if (vars.count("mustResolve")) { ret->mustResolve = boost::get(vars["mustResolve"]); } if (vars.count("useClientSubnet")) { ret->useECS = boost::get(vars["useClientSubnet"]); } if (vars.count("useProxyProtocol")) { ret->useProxyProtocol = boost::get(vars["useProxyProtocol"]); } if (vars.count("disableZeroScope")) { ret->disableZeroScope = boost::get(vars["disableZeroScope"]); } if (vars.count("ipBindAddrNoPort")) { ret->ipBindAddrNoPort = boost::get(vars["ipBindAddrNoPort"]); } if (vars.count("addXPF")) { ret->xpfRRCode = std::stoi(boost::get(vars["addXPF"])); } if (vars.count("maxCheckFailures")) { ret->maxCheckFailures = std::stoi(boost::get(vars["maxCheckFailures"])); } if (vars.count("rise")) { ret->minRiseSuccesses = std::stoi(boost::get(vars["rise"])); } if (vars.count("reconnectOnUp")) { ret->reconnectOnUp = boost::get(vars["reconnectOnUp"]); } if (vars.count("cpus")) { for (const auto& cpu : boost::get>>(vars["cpus"])) { cpus.insert(std::stoi(cpu.second)); } } if (vars.count("tcpOnly")) { ret->d_tcpOnly = boost::get(vars.at("tcpOnly")); } if (vars.count("tls")) { TLSContextParameters tlsParams; std::string ciphers; std::string ciphers13; tlsParams.d_provider = boost::get(vars.at("tls")); if (vars.count("ciphers")) { tlsParams.d_ciphers = boost::get(vars.at("ciphers")); } if (vars.count("ciphers13")) { tlsParams.d_ciphers13 = boost::get(vars.at("ciphers13")); } if (vars.count("caStore")) { tlsParams.d_caStore = boost::get(vars.at("caStore")); } if (vars.count("validateCertificates")) { tlsParams.d_validateCertificates = boost::get(vars.at("validateCertificates")); } if (vars.count("releaseBuffers")) { tlsParams.d_releaseBuffers = boost::get(vars.at("releaseBuffers")); } if (vars.count("enableRenegotiation")) { tlsParams.d_enableRenegotiation = boost::get(vars.at("enableRenegotiation")); } if (vars.count("subjectName")) { ret->d_tlsSubjectName = boost::get(vars.at("subjectName")); } ret->d_tlsCtx = getTLSContext(tlsParams); if (vars.count("dohPath")) { #ifdef HAVE_NGHTTP2 ret->d_dohPath = boost::get(vars.at("dohPath")); if (ret->d_tlsCtx) { setupDoHClientProtocolNegotiation(ret->d_tlsCtx); } if (g_configurationDone && g_outgoingDoHWorkerThreads && *g_outgoingDoHWorkerThreads == 0) { throw std::runtime_error("Error: setOutgoingDoHWorkerThreads() is set to 0 so no outgoing DoH worker thread is available to serve queries"); } if (!g_outgoingDoHWorkerThreads || *g_outgoingDoHWorkerThreads == 0) { g_outgoingDoHWorkerThreads = 1; } if (vars.count("addXForwardedHeaders")) { ret->d_addXForwardedHeaders = boost::get(vars.at("addXForwardedHeaders")); } #else /* HAVE_NGHTTP2 */ throw std::runtime_error("Outgoing DNS over HTTPS support requested (via 'dohPath' on newServer()) but nghttp2 support is not available"); #endif /* HAVE_NGHTTP2 */ } else { setupDoTProtocolNegotiation(ret->d_tlsCtx); } } if (!ret->isTCPOnly() && !(client || configCheck)) { if (!IsAnyAddress(ret->remote)) { ret->connectUDPSockets(numberOfSockets); } } /* this needs to be done _AFTER_ the order has been set, since the server are kept ordered inside the pool */ auto localPools = g_pools.getCopy(); if (vars.count("pool")) { if (auto* pool = boost::get(&vars["pool"])) { ret->pools.insert(*pool); } else { auto pools = boost::get>>(vars["pool"]); for (auto& p : pools) { ret->pools.insert(p.second); } } for (const auto& poolName : ret->pools) { addServerToPool(localPools, poolName, ret); } } else { addServerToPool(localPools, "", ret); } g_pools.setState(localPools); if (ret->connected) { ret->threadStarted.test_and_set(); if (g_launchWork) { g_launchWork->push_back([ret, cpus]() { ret->tid = thread(responderThread, ret); if (!cpus.empty()) { mapThreadToCPUList(ret->tid.native_handle(), cpus); } }); } else { ret->tid = thread(responderThread, ret); if (!cpus.empty()) { mapThreadToCPUList(ret->tid.native_handle(), cpus); } } } auto states = g_dstates.getCopy(); states.push_back(ret); std::stable_sort(states.begin(), states.end(), [](const decltype(ret)& a, const decltype(ret)& b) { return a->order < b->order; }); g_dstates.setState(states); return ret; }); luaCtx.writeFunction("rmServer", [](boost::variant, int, std::string> var) { setLuaSideEffect(); shared_ptr server = nullptr; auto states = g_dstates.getCopy(); if (auto* rem = boost::get>(&var)) { server = *rem; } else if (auto str = boost::get(&var)) { const auto uuid = getUniqueID(*str); for (auto& state : states) { if (state->id == uuid) { server = state; } } } else { int idx = boost::get(var); server = states.at(idx); } if (!server) { throw std::runtime_error("unable to locate the requested server"); } auto localPools = g_pools.getCopy(); for (const string& poolName : server->pools) { removeServerFromPool(localPools, poolName, server); } /* the server might also be in the default pool */ removeServerFromPool(localPools, "", server); g_pools.setState(localPools); states.erase(remove(states.begin(), states.end(), server), states.end()); g_dstates.setState(states); server->stop(); }); luaCtx.writeFunction("truncateTC", [](bool tc) { setLuaSideEffect(); g_truncateTC=tc; }); luaCtx.writeFunction("fixupCase", [](bool fu) { setLuaSideEffect(); g_fixupCase=fu; }); luaCtx.writeFunction("addACL", [](const std::string& domain) { setLuaSideEffect(); g_ACL.modify([domain](NetmaskGroup& nmg) { nmg.addMask(domain); }); }); luaCtx.writeFunction("rmACL", [](const std::string& netmask) { setLuaSideEffect(); g_ACL.modify([netmask](NetmaskGroup& nmg) { nmg.deleteMask(netmask); }); }); luaCtx.writeFunction("setLocal", [client](const std::string& addr, boost::optional vars) { setLuaSideEffect(); if (client) return; if (g_configurationDone) { g_outputBuffer = "setLocal cannot be used at runtime!\n"; return; } bool reusePort = false; int tcpFastOpenQueueSize = 0; int tcpListenQueueSize = 0; size_t maxInFlightQueriesPerConn = 0; size_t tcpMaxConcurrentConnections = 0; std::string interface; std::set cpus; parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConnections); try { ComboAddress loc(addr, 53); for (auto it = g_frontends.begin(); it != g_frontends.end();) { /* DoH, DoT and DNSCrypt frontends are separate */ if ((*it)->tlsFrontend == nullptr && (*it)->dnscryptCtx == nullptr && (*it)->dohFrontend == nullptr) { it = g_frontends.erase(it); } else { ++it; } } // only works pre-startup, so no sync necessary g_frontends.push_back(std::make_unique(loc, false, reusePort, tcpFastOpenQueueSize, interface, cpus)); auto tcpCS = std::make_unique(loc, true, reusePort, tcpFastOpenQueueSize, interface, cpus); if (tcpListenQueueSize > 0) { tcpCS->tcpListenQueueSize = tcpListenQueueSize; } if (maxInFlightQueriesPerConn > 0) { tcpCS->d_maxInFlightQueriesPerConn = maxInFlightQueriesPerConn; } if (tcpMaxConcurrentConnections > 0) { tcpCS->d_tcpConcurrentConnectionsLimit = tcpMaxConcurrentConnections; } g_frontends.push_back(std::move(tcpCS)); } catch (const std::exception& e) { g_outputBuffer = "Error: " + string(e.what()) + "\n"; } }); luaCtx.writeFunction("addLocal", [client](const std::string& addr, boost::optional vars) { setLuaSideEffect(); if (client) return; if (g_configurationDone) { g_outputBuffer = "addLocal cannot be used at runtime!\n"; return; } bool reusePort = false; int tcpFastOpenQueueSize = 0; int tcpListenQueueSize = 0; size_t maxInFlightQueriesPerConn = 0; size_t tcpMaxConcurrentConnections = 0; std::string interface; std::set cpus; parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConnections); try { ComboAddress loc(addr, 53); // only works pre-startup, so no sync necessary g_frontends.push_back(std::make_unique(loc, false, reusePort, tcpFastOpenQueueSize, interface, cpus)); auto tcpCS = std::make_unique(loc, true, reusePort, tcpFastOpenQueueSize, interface, cpus); if (tcpListenQueueSize > 0) { tcpCS->tcpListenQueueSize = tcpListenQueueSize; } if (maxInFlightQueriesPerConn > 0) { tcpCS->d_maxInFlightQueriesPerConn = maxInFlightQueriesPerConn; } if (tcpMaxConcurrentConnections > 0) { tcpCS->d_tcpConcurrentConnectionsLimit = tcpMaxConcurrentConnections; } g_frontends.push_back(std::move(tcpCS)); } catch (std::exception& e) { g_outputBuffer = "Error: " + string(e.what()) + "\n"; errlog("Error while trying to listen on %s: %s\n", addr, string(e.what())); } }); luaCtx.writeFunction("setACL", [](boost::variant>> inp) { setLuaSideEffect(); NetmaskGroup nmg; if (auto str = boost::get(&inp)) { nmg.addMask(*str); } else for (const auto& p : boost::get>>(inp)) { nmg.addMask(p.second); } g_ACL.setState(nmg); }); luaCtx.writeFunction("setACLFromFile", [](const std::string& file) { setLuaSideEffect(); NetmaskGroup nmg; ifstream ifs(file); if (!ifs) { throw std::runtime_error("Could not open '" + file + "': " + stringerror()); } string::size_type pos; string line; while (getline(ifs, line)) { pos = line.find('#'); if (pos != string::npos) line.resize(pos); boost::trim(line); if (line.empty()) continue; nmg.addMask(line); } g_ACL.setState(nmg); }); luaCtx.writeFunction("showACL", []() { setLuaNoSideEffect(); vector vec; g_ACL.getLocal()->toStringVector(&vec); for (const auto& s : vec) g_outputBuffer += s + "\n"; }); luaCtx.writeFunction("shutdown", []() { #ifdef HAVE_SYSTEMD sd_notify(0, "STOPPING=1"); #endif /* HAVE_SYSTEMD */ #if 0 // Useful for debugging leaks, but might lead to race under load // since other threads are still running. for(auto& frontend : g_tlslocals) { frontend->cleanup(); } g_tlslocals.clear(); g_rings.clear(); #endif /* 0 */ _exit(0); }); typedef std::unordered_map> showserversopts_t; luaCtx.writeFunction("showServers", [](boost::optional vars) { setLuaNoSideEffect(); bool showUUIDs = false; if (vars) { if (vars->count("showUUIDs")) { showUUIDs = boost::get((*vars)["showUUIDs"]); } } try { ostringstream ret; boost::format fmt; if (showUUIDs) { fmt = boost::format("%1$-3d %15$-36s %2$-20.20s %|62t|%3% %|92t|%4$5s %|88t|%5$7.1f %|103t|%6$7d %|106t|%7$3d %|115t|%8$2d %|117t|%9$10d %|123t|%10$7d %|128t|%11$5.1f %|146t|%12$5.1f %|152t|%13$11d %14%"); // 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ret << (fmt % "#" % "Name" % "Address" % "State" % "Qps" % "Qlim" % "Ord" % "Wt" % "Queries" % "Drops" % "Drate" % "Lat" % "Outstanding" % "Pools" % "UUID") << endl; } else { fmt = boost::format("%1$-3d %2$-20.20s %|25t|%3% %|55t|%4$5s %|51t|%5$7.1f %|66t|%6$7d %|69t|%7$3d %|78t|%8$2d %|80t|%9$10d %|86t|%10$7d %|91t|%11$5.1f %|109t|%12$5.1f %|115t|%13$11d %14%"); ret << (fmt % "#" % "Name" % "Address" % "State" % "Qps" % "Qlim" % "Ord" % "Wt" % "Queries" % "Drops" % "Drate" % "Lat" % "Outstanding" % "Pools") << endl; } uint64_t totQPS{0}, totQueries{0}, totDrops{0}; int counter = 0; auto states = g_dstates.getLocal(); for (const auto& s : *states) { string status = s->getStatus(); string pools; for (auto& p : s->pools) { if (!pools.empty()) pools += " "; pools += p; } if (showUUIDs) { ret << (fmt % counter % s->getName() % s->remote.toStringWithPort() % status % s->queryLoad % s->qps.getRate() % s->order % s->weight % s->queries.load() % s->reuseds.load() % (s->dropRate) % (s->latencyUsec / 1000.0) % s->outstanding.load() % pools % s->id) << endl; } else { ret << (fmt % counter % s->getName() % s->remote.toStringWithPort() % status % s->queryLoad % s->qps.getRate() % s->order % s->weight % s->queries.load() % s->reuseds.load() % (s->dropRate) % (s->latencyUsec / 1000.0) % s->outstanding.load() % pools) << endl; } totQPS += s->queryLoad; totQueries += s->queries.load(); totDrops += s->reuseds.load(); ++counter; } if (showUUIDs) { ret << (fmt % "All" % "" % "" % "" % (double)totQPS % "" % "" % "" % totQueries % totDrops % "" % "" % "" % "" % "") << endl; } else { ret << (fmt % "All" % "" % "" % "" % (double)totQPS % "" % "" % "" % totQueries % totDrops % "" % "" % "" % "") << endl; } g_outputBuffer = ret.str(); } catch (std::exception& e) { g_outputBuffer = e.what(); throw; } }); luaCtx.writeFunction("getServers", []() { setLuaNoSideEffect(); vector>> ret; int count = 1; for (const auto& s : g_dstates.getCopy()) { ret.push_back(make_pair(count++, s)); } return ret; }); luaCtx.writeFunction("getPoolServers", [](string pool) { const auto poolServers = getDownstreamCandidates(g_pools.getCopy(), pool); return *poolServers; }); luaCtx.writeFunction("getServer", [client](boost::variant i) { if (client) { return std::make_shared(ComboAddress()); } auto states = g_dstates.getCopy(); if (auto str = boost::get(&i)) { const auto uuid = getUniqueID(*str); for (auto& state : states) { if (state->id == uuid) { return state; } } } else if (auto pos = boost::get(&i)) { return states.at(*pos); } g_outputBuffer = "Error: no rule matched\n"; return std::shared_ptr(nullptr); }); luaCtx.writeFunction("carbonServer", [](const std::string& address, boost::optional ourName, boost::optional interval, boost::optional namespace_name, boost::optional instance_name) { setLuaSideEffect(); auto ours = g_carbon.getCopy(); ours.push_back({ComboAddress(address, 2003), (namespace_name && !namespace_name->empty()) ? *namespace_name : "dnsdist", ourName ? *ourName : "", (instance_name && !instance_name->empty()) ? *instance_name : "main", interval ? *interval : 30}); g_carbon.setState(ours); }); luaCtx.writeFunction("webserver", [client, configCheck](const std::string& address, boost::optional password, boost::optional apiKey, const boost::optional> customHeaders, const boost::optional acl) { setLuaSideEffect(); ComboAddress local; try { local = ComboAddress(address); } catch (const PDNSException& e) { throw std::runtime_error(std::string("Error parsing the bind address for the webserver: ") + e.reason); } if (client || configCheck) { return; } if (password || apiKey || customHeaders || acl) { warnlog("Passing additional parameters to 'webserver()' is deprecated, please use 'setWebserverConfig()' instead."); } try { int sock = SSocket(local.sin4.sin_family, SOCK_STREAM, 0); SSetsockopt(sock, SOL_SOCKET, SO_REUSEADDR, 1); SBind(sock, local); SListen(sock, 5); auto launch = [sock, local, password, apiKey, customHeaders, acl]() { if (password) { auto holder = make_unique(std::string(*password), false); if (!holder->wasHashed() && holder->isHashingAvailable()) { infolog("Passing a plain-text password to 'webserver()' is deprecated, please use 'setWebserverConfig()' instead and consider generating a hashed password using 'hashPassword()'."); } setWebserverPassword(std::move(holder)); } if (apiKey) { auto holder = make_unique(std::string(*apiKey), false); setWebserverAPIKey(std::move(holder)); } if (customHeaders) { setWebserverCustomHeaders(customHeaders); } if (acl) { setWebserverACL(*acl); } thread t(dnsdistWebserverThread, sock, local); t.detach(); }; if (g_launchWork) { g_launchWork->push_back(launch); } else { launch(); } } catch (const std::exception& e) { g_outputBuffer = "Unable to bind to webserver socket on " + local.toStringWithPort() + ": " + e.what(); errlog("Unable to bind to webserver socket on %s: %s", local.toStringWithPort(), e.what()); } }); typedef std::unordered_map>> webserveropts_t; luaCtx.writeFunction("setWebserverConfig", [](boost::optional vars) { setLuaSideEffect(); if (!vars) { return; } bool hashPlaintextCredentials = false; if (vars->count("hashPlaintextCredentials")) { hashPlaintextCredentials = boost::get(vars->at("hashPlaintextCredentials")); } if (vars->count("password")) { std::string password = boost::get(vars->at("password")); auto holder = make_unique(std::move(password), hashPlaintextCredentials); if (!holder->wasHashed() && holder->isHashingAvailable()) { infolog("Passing a plain-text password via the 'password' parameter to 'setWebserverConfig()' is not advised, please consider generating a hashed one using 'hashPassword()' instead."); } setWebserverPassword(std::move(holder)); } if (vars->count("apiKey")) { std::string apiKey = boost::get(vars->at("apiKey")); auto holder = make_unique(std::move(apiKey), hashPlaintextCredentials); if (!holder->wasHashed() && holder->isHashingAvailable()) { infolog("Passing a plain-text API key via the 'apiKey' parameter to 'setWebserverConfig()' is not advised, please consider generating a hashed one using 'hashPassword()' instead."); } setWebserverAPIKey(std::move(holder)); } if (vars->count("acl")) { const std::string acl = boost::get(vars->at("acl")); setWebserverACL(acl); } if (vars->count("customHeaders")) { const boost::optional> headers = boost::get>(vars->at("customHeaders")); setWebserverCustomHeaders(headers); } if (vars->count("statsRequireAuthentication")) { setWebserverStatsRequireAuthentication(boost::get(vars->at("statsRequireAuthentication"))); } if (vars->count("maxConcurrentConnections")) { setWebserverMaxConcurrentConnections(std::stoi(boost::get(vars->at("maxConcurrentConnections")))); } }); luaCtx.writeFunction("showWebserverConfig", []() { setLuaNoSideEffect(); return getWebserverConfig(); }); luaCtx.writeFunction("hashPassword", [](const std::string& password, boost::optional workFactor) { if (workFactor) { return hashPassword(password, *workFactor, CredentialsHolder::s_defaultParallelFactor, CredentialsHolder::s_defaultBlockSize); } return hashPassword(password); }); luaCtx.writeFunction("controlSocket", [client, configCheck](const std::string& str) { setLuaSideEffect(); ComboAddress local(str, 5199); if (client || configCheck) { g_serverControl = local; return; } g_consoleEnabled = true; #ifdef HAVE_LIBSODIUM if (g_configurationDone && g_consoleKey.empty()) { warnlog("Warning, the console has been enabled via 'controlSocket()' but no key has been set with 'setKey()' so all connections will fail until a key has been set"); } #endif try { int sock = SSocket(local.sin4.sin_family, SOCK_STREAM, 0); SSetsockopt(sock, SOL_SOCKET, SO_REUSEADDR, 1); SBind(sock, local); SListen(sock, 5); auto launch = [sock, local]() { thread t(controlThread, sock, local); t.detach(); }; if (g_launchWork) { g_launchWork->push_back(launch); } else { launch(); } } catch (std::exception& e) { g_outputBuffer = "Unable to bind to control socket on " + local.toStringWithPort() + ": " + e.what(); errlog("Unable to bind to control socket on %s: %s", local.toStringWithPort(), e.what()); } }); luaCtx.writeFunction("addConsoleACL", [](const std::string& netmask) { setLuaSideEffect(); #ifndef HAVE_LIBSODIUM warnlog("Allowing remote access to the console while libsodium support has not been enabled is not secure, and will result in cleartext communications"); #endif g_consoleACL.modify([netmask](NetmaskGroup& nmg) { nmg.addMask(netmask); }); }); luaCtx.writeFunction("setConsoleACL", [](boost::variant>> inp) { setLuaSideEffect(); #ifndef HAVE_LIBSODIUM warnlog("Allowing remote access to the console while libsodium support has not been enabled is not secure, and will result in cleartext communications"); #endif NetmaskGroup nmg; if (auto str = boost::get(&inp)) { nmg.addMask(*str); } else for (const auto& p : boost::get>>(inp)) { nmg.addMask(p.second); } g_consoleACL.setState(nmg); }); luaCtx.writeFunction("showConsoleACL", []() { setLuaNoSideEffect(); #ifndef HAVE_LIBSODIUM warnlog("Allowing remote access to the console while libsodium support has not been enabled is not secure, and will result in cleartext communications"); #endif vector vec; g_consoleACL.getLocal()->toStringVector(&vec); for (const auto& s : vec) { g_outputBuffer += s + "\n"; } }); luaCtx.writeFunction("setConsoleMaximumConcurrentConnections", [](size_t max) { setLuaSideEffect(); setConsoleMaximumConcurrentConnections(max); }); luaCtx.writeFunction("clearQueryCounters", []() { unsigned int size{0}; { auto records = g_qcount.records.write_lock(); size = records->size(); records->clear(); } boost::format fmt("%d records cleared from query counter buffer\n"); g_outputBuffer = (fmt % size).str(); }); luaCtx.writeFunction("getQueryCounters", [](boost::optional optMax) { setLuaNoSideEffect(); auto records = g_qcount.records.read_lock(); g_outputBuffer = "query counting is currently: "; g_outputBuffer += g_qcount.enabled ? "enabled" : "disabled"; g_outputBuffer += (boost::format(" (%d records in buffer)\n") % records->size()).str(); boost::format fmt("%-3d %s: %d request(s)\n"); unsigned int max = optMax ? *optMax : 10; unsigned int index{1}; for (auto it = records->begin(); it != records->end() && index <= max; ++it, ++index) { g_outputBuffer += (fmt % index % it->first % it->second).str(); } }); luaCtx.writeFunction("setQueryCount", [](bool enabled) { g_qcount.enabled = enabled; }); luaCtx.writeFunction("setQueryCountFilter", [](QueryCountFilter func) { g_qcount.filter = func; }); luaCtx.writeFunction("makeKey", []() { setLuaNoSideEffect(); g_outputBuffer = "setKey(" + newKey() + ")\n"; }); luaCtx.writeFunction("setKey", [](const std::string& key) { if (!g_configurationDone && !g_consoleKey.empty()) { // this makes sure the commandline -k key prevails over dnsdist.conf return; // but later setKeys() trump the -k value again } #ifndef HAVE_LIBSODIUM warnlog("Calling setKey() while libsodium support has not been enabled is not secure, and will result in cleartext communications"); #endif setLuaSideEffect(); string newkey; if (B64Decode(key, newkey) < 0) { g_outputBuffer = string("Unable to decode ") + key + " as Base64"; errlog("%s", g_outputBuffer); } else g_consoleKey = newkey; }); luaCtx.writeFunction("clearConsoleHistory", []() { clearConsoleHistory(); }); luaCtx.writeFunction("testCrypto", [](boost::optional optTestMsg) { setLuaNoSideEffect(); #ifdef HAVE_LIBSODIUM try { string testmsg; if (optTestMsg) { testmsg = *optTestMsg; } else { testmsg = "testStringForCryptoTests"; } SodiumNonce sn, sn2; sn.init(); sn2 = sn; string encrypted = sodEncryptSym(testmsg, g_consoleKey, sn); string decrypted = sodDecryptSym(encrypted, g_consoleKey, sn2); sn.increment(); sn2.increment(); encrypted = sodEncryptSym(testmsg, g_consoleKey, sn); decrypted = sodDecryptSym(encrypted, g_consoleKey, sn2); if (testmsg == decrypted) g_outputBuffer = "Everything is ok!\n"; else g_outputBuffer = "Crypto failed.. (the decoded value does not match the cleartext one)\n"; } catch (const std::exception& e) { g_outputBuffer = "Crypto failed: " + std::string(e.what()) + "\n"; } catch (...) { g_outputBuffer = "Crypto failed..\n"; } #else g_outputBuffer = "Crypto not available.\n"; #endif }); luaCtx.writeFunction("setTCPRecvTimeout", [](int timeout) { g_tcpRecvTimeout = timeout; }); luaCtx.writeFunction("setTCPSendTimeout", [](int timeout) { g_tcpSendTimeout = timeout; }); luaCtx.writeFunction("setUDPTimeout", [](int timeout) { g_udpTimeout = timeout; }); luaCtx.writeFunction("setMaxUDPOutstanding", [](uint64_t max) { if (!g_configurationDone) { checkParameterBound("setMaxUDPOutstanding", max); g_maxOutstanding = max; } else { g_outputBuffer = "Max UDP outstanding cannot be altered at runtime!\n"; } }); luaCtx.writeFunction("setMaxTCPClientThreads", [](uint64_t max) { if (!g_configurationDone) { g_maxTCPClientThreads = max; } else { g_outputBuffer = "Maximum TCP client threads count cannot be altered at runtime!\n"; } }); luaCtx.writeFunction("setMaxTCPQueuedConnections", [](uint64_t max) { if (!g_configurationDone) { g_maxTCPQueuedConnections = max; } else { g_outputBuffer = "The maximum number of queued TCP connections cannot be altered at runtime!\n"; } }); luaCtx.writeFunction("setMaxTCPQueriesPerConnection", [](size_t max) { if (!g_configurationDone) { g_maxTCPQueriesPerConn = max; } else { g_outputBuffer = "The maximum number of queries per TCP connection cannot be altered at runtime!\n"; } }); luaCtx.writeFunction("setMaxTCPConnectionsPerClient", [](size_t max) { if (!g_configurationDone) { g_maxTCPConnectionsPerClient = max; } else { g_outputBuffer = "The maximum number of TCP connection per client cannot be altered at runtime!\n"; } }); luaCtx.writeFunction("setMaxTCPConnectionDuration", [](size_t max) { if (!g_configurationDone) { g_maxTCPConnectionDuration = max; } else { g_outputBuffer = "The maximum duration of a TCP connection cannot be altered at runtime!\n"; } }); luaCtx.writeFunction("setMaxCachedTCPConnectionsPerDownstream", [](size_t max) { DownstreamTCPConnectionsManager::setMaxIdleConnectionsPerDownstream(max); }); luaCtx.writeFunction("setMaxIdleDoHConnectionsPerDownstream", [](size_t max) { setDoHDownstreamMaxIdleConnectionsPerBackend(max); }); luaCtx.writeFunction("setOutgoingDoHWorkerThreads", [](uint64_t workers) { if (!g_configurationDone) { g_outgoingDoHWorkerThreads = workers; } else { g_outputBuffer = "The amount of outgoing DoH worker threads cannot be altered at runtime!\n"; } }); luaCtx.writeFunction("setOutgoingTLSSessionsCacheMaxTicketsPerBackend", [](uint64_t max) { if (g_configurationDone) { g_outputBuffer = "setOutgoingTLSSessionsCacheMaxTicketsPerBackend() cannot be called at runtime!\n"; return; } TLSSessionCache::setMaxTicketsPerBackend(max); }); luaCtx.writeFunction("setOutgoingTLSSessionsCacheCleanupDelay", [](time_t delay) { if (g_configurationDone) { g_outputBuffer = "setOutgoingTLSSessionsCacheCleanupDelay() cannot be called at runtime!\n"; return; } TLSSessionCache::setCleanupDelay(delay); }); luaCtx.writeFunction("setOutgoingTLSSessionsCacheMaxTicketValidity", [](time_t validity) { if (g_configurationDone) { g_outputBuffer = "setOutgoingTLSSessionsCacheMaxTicketValidity() cannot be called at runtime!\n"; return; } TLSSessionCache::setSessionValidity(validity); }); luaCtx.writeFunction("getOutgoingTLSSessionCacheSize", []() { setLuaNoSideEffect(); return g_sessionCache.getSize(); }); luaCtx.writeFunction("setCacheCleaningDelay", [](uint64_t delay) { checkParameterBound("setCacheCleaningDelay", delay, std::numeric_limits::max()); g_cacheCleaningDelay = delay; }); luaCtx.writeFunction("setCacheCleaningPercentage", [](uint16_t percentage) { if (percentage < 100) g_cacheCleaningPercentage = percentage; else g_cacheCleaningPercentage = 100; }); luaCtx.writeFunction("setECSSourcePrefixV4", [](uint16_t prefix) { g_ECSSourcePrefixV4 = prefix; }); luaCtx.writeFunction("setECSSourcePrefixV6", [](uint16_t prefix) { g_ECSSourcePrefixV6 = prefix; }); luaCtx.writeFunction("setECSOverride", [](bool override) { g_ECSOverride = override; }); luaCtx.writeFunction("showDynBlocks", []() { setLuaNoSideEffect(); auto slow = g_dynblockNMG.getCopy(); struct timespec now; gettime(&now); boost::format fmt("%-24s %8d %8d %-10s %-20s %s\n"); g_outputBuffer = (fmt % "What" % "Seconds" % "Blocks" % "Warning" % "Action" % "Reason").str(); for (const auto& e : slow) { if (now < e.second.until) { uint64_t counter = e.second.blocks; if (g_defaultBPFFilter && e.second.bpf) { counter += g_defaultBPFFilter->getHits(e.first.getNetwork()); } g_outputBuffer += (fmt % e.first.toString() % (e.second.until.tv_sec - now.tv_sec) % counter % (e.second.warning ? "true" : "false") % DNSAction::typeToString(e.second.action != DNSAction::Action::None ? e.second.action : g_dynBlockAction) % e.second.reason).str(); } } auto slow2 = g_dynblockSMT.getCopy(); slow2.visit([&now, &fmt](const SuffixMatchTree& node) { if (now < node.d_value.until) { string dom("empty"); if (!node.d_value.domain.empty()) dom = node.d_value.domain.toString(); g_outputBuffer += (fmt % dom % (node.d_value.until.tv_sec - now.tv_sec) % node.d_value.blocks % (node.d_value.warning ? "true" : "false") % DNSAction::typeToString(node.d_value.action != DNSAction::Action::None ? node.d_value.action : g_dynBlockAction) % node.d_value.reason).str(); } }); }); luaCtx.writeFunction("clearDynBlocks", []() { setLuaSideEffect(); nmts_t nmg; g_dynblockNMG.setState(nmg); SuffixMatchTree smt; g_dynblockSMT.setState(smt); }); luaCtx.writeFunction("addDynBlocks", [](const std::unordered_map& m, const std::string& msg, boost::optional seconds, boost::optional action) { if (m.empty()) { return; } setLuaSideEffect(); auto slow = g_dynblockNMG.getCopy(); struct timespec until, now; gettime(&now); until = now; int actualSeconds = seconds ? *seconds : 10; until.tv_sec += actualSeconds; for (const auto& capair : m) { unsigned int count = 0; /* this legacy interface does not support ranges or ports, use DynBlockRulesGroup instead */ AddressAndPortRange requestor(capair.first, capair.first.isIPv4() ? 32 : 128, 0); auto got = slow.lookup(requestor); bool expired = false; if (got) { if (until < got->second.until) { // had a longer policy continue; } if (now < got->second.until) { // only inherit count on fresh query we are extending count = got->second.blocks; } else { expired = true; } } DynBlock db{msg, until, DNSName(), (action ? *action : DNSAction::Action::None)}; db.blocks = count; if (!got || expired) { warnlog("Inserting dynamic block for %s for %d seconds: %s", capair.first.toString(), actualSeconds, msg); } slow.insert(requestor).second = db; } g_dynblockNMG.setState(slow); }); luaCtx.writeFunction("addDynBlockSMT", [](const vector>& names, const std::string& msg, boost::optional seconds, boost::optional action) { if (names.empty()) { return; } setLuaSideEffect(); auto slow = g_dynblockSMT.getCopy(); struct timespec until, now; gettime(&now); until = now; int actualSeconds = seconds ? *seconds : 10; until.tv_sec += actualSeconds; for (const auto& capair : names) { unsigned int count = 0; DNSName domain(capair.second); domain.makeUsLowerCase(); auto got = slow.lookup(domain); bool expired = false; if (got) { if (until < got->until) // had a longer policy continue; if (now < got->until) // only inherit count on fresh query we are extending count = got->blocks; else expired = true; } DynBlock db{msg, until, domain, (action ? *action : DNSAction::Action::None)}; db.blocks = count; if (!got || expired) warnlog("Inserting dynamic block for %s for %d seconds: %s", domain, actualSeconds, msg); slow.add(domain, std::move(db)); } g_dynblockSMT.setState(slow); }); luaCtx.writeFunction("setDynBlocksAction", [](DNSAction::Action action) { if (!g_configurationDone) { if (action == DNSAction::Action::Drop || action == DNSAction::Action::NoOp || action == DNSAction::Action::Nxdomain || action == DNSAction::Action::Refused || action == DNSAction::Action::Truncate || action == DNSAction::Action::NoRecurse) { g_dynBlockAction = action; } else { errlog("Dynamic blocks action can only be Drop, NoOp, NXDomain, Refused, Truncate or NoRecurse!"); g_outputBuffer = "Dynamic blocks action can only be Drop, NoOp, NXDomain, Refused, Truncate or NoRecurse!\n"; } } else { g_outputBuffer = "Dynamic blocks action cannot be altered at runtime!\n"; } }); luaCtx.writeFunction("setDynBlocksPurgeInterval", [](unsigned int interval) { DynBlockMaintenance::s_expiredDynBlocksPurgeInterval = interval; }); luaCtx.writeFunction("addDNSCryptBind", [](const std::string& addr, const std::string& providerName, boost::variant>> certFiles, boost::variant>> keyFiles, boost::optional vars) { if (g_configurationDone) { g_outputBuffer = "addDNSCryptBind cannot be used at runtime!\n"; return; } #ifdef HAVE_DNSCRYPT bool reusePort = false; int tcpFastOpenQueueSize = 0; int tcpListenQueueSize = 0; size_t maxInFlightQueriesPerConn = 0; size_t tcpMaxConcurrentConnections = 0; std::string interface; std::set cpus; std::vector certKeys; parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConnections); if (certFiles.type() == typeid(std::string) && keyFiles.type() == typeid(std::string)) { auto certFile = boost::get(certFiles); auto keyFile = boost::get(keyFiles); certKeys.push_back({certFile, keyFile}); } else if (certFiles.type() == typeid(std::vector>) && keyFiles.type() == typeid(std::vector>)) { auto certFilesVect = boost::get>>(certFiles); auto keyFilesVect = boost::get>>(keyFiles); if (certFilesVect.size() == keyFilesVect.size()) { for (size_t idx = 0; idx < certFilesVect.size(); idx++) { certKeys.push_back({certFilesVect.at(idx).second, keyFilesVect.at(idx).second}); } } else { errlog("Error, mismatching number of certificates and keys in call to addDNSCryptBind!"); g_outputBuffer = "Error, mismatching number of certificates and keys in call to addDNSCryptBind()!"; return; } } else { errlog("Error, mismatching number of certificates and keys in call to addDNSCryptBind()!"); g_outputBuffer = "Error, mismatching number of certificates and keys in call to addDNSCryptBind()!"; return; } try { auto ctx = std::make_shared(providerName, certKeys); /* UDP */ auto cs = std::make_unique(ComboAddress(addr, 443), false, reusePort, tcpFastOpenQueueSize, interface, cpus); cs->dnscryptCtx = ctx; g_dnsCryptLocals.push_back(ctx); g_frontends.push_back(std::move(cs)); /* TCP */ cs = std::make_unique(ComboAddress(addr, 443), true, reusePort, tcpFastOpenQueueSize, interface, cpus); cs->dnscryptCtx = ctx; if (tcpListenQueueSize > 0) { cs->tcpListenQueueSize = tcpListenQueueSize; } if (maxInFlightQueriesPerConn > 0) { cs->d_maxInFlightQueriesPerConn = maxInFlightQueriesPerConn; } if (tcpMaxConcurrentConnections > 0) { cs->d_tcpConcurrentConnectionsLimit = tcpMaxConcurrentConnections; } g_frontends.push_back(std::move(cs)); } catch (std::exception& e) { errlog(e.what()); g_outputBuffer = "Error: " + string(e.what()) + "\n"; } #else g_outputBuffer = "Error: DNSCrypt support is not enabled.\n"; #endif }); luaCtx.writeFunction("showDNSCryptBinds", []() { setLuaNoSideEffect(); #ifdef HAVE_DNSCRYPT ostringstream ret; boost::format fmt("%1$-3d %2% %|25t|%3$-20.20s"); ret << (fmt % "#" % "Address" % "Provider Name") << endl; size_t idx = 0; std::unordered_set> contexts; for (const auto& frontend : g_frontends) { const std::shared_ptr ctx = frontend->dnscryptCtx; if (!ctx || contexts.count(ctx) != 0) { continue; } contexts.insert(ctx); ret << (fmt % idx % frontend->local.toStringWithPort() % ctx->getProviderName()) << endl; idx++; } g_outputBuffer = ret.str(); #else g_outputBuffer = "Error: DNSCrypt support is not enabled.\n"; #endif }); luaCtx.writeFunction("getDNSCryptBind", [](size_t idx) { setLuaNoSideEffect(); #ifdef HAVE_DNSCRYPT std::shared_ptr ret = nullptr; if (idx < g_dnsCryptLocals.size()) { ret = g_dnsCryptLocals.at(idx); } return ret; #else g_outputBuffer = "Error: DNSCrypt support is not enabled.\n"; #endif }); luaCtx.writeFunction("getDNSCryptBindCount", []() { setLuaNoSideEffect(); return g_dnsCryptLocals.size(); }); luaCtx.writeFunction("generateDNSCryptProviderKeys", [client](const std::string& publicKeyFile, const std::string privateKeyFile) { setLuaNoSideEffect(); #ifdef HAVE_DNSCRYPT if (client) { return; } unsigned char publicKey[DNSCRYPT_PROVIDER_PUBLIC_KEY_SIZE]; unsigned char privateKey[DNSCRYPT_PROVIDER_PRIVATE_KEY_SIZE]; sodium_mlock(privateKey, sizeof(privateKey)); try { DNSCryptContext::generateProviderKeys(publicKey, privateKey); ofstream pubKStream(publicKeyFile); pubKStream.write((char*)publicKey, sizeof(publicKey)); pubKStream.close(); ofstream privKStream(privateKeyFile); privKStream.write((char*)privateKey, sizeof(privateKey)); privKStream.close(); g_outputBuffer = "Provider fingerprint is: " + DNSCryptContext::getProviderFingerprint(publicKey) + "\n"; } catch (std::exception& e) { errlog(e.what()); g_outputBuffer = "Error: " + string(e.what()) + "\n"; } sodium_memzero(privateKey, sizeof(privateKey)); sodium_munlock(privateKey, sizeof(privateKey)); #else g_outputBuffer = "Error: DNSCrypt support is not enabled.\n"; #endif }); luaCtx.writeFunction("printDNSCryptProviderFingerprint", [](const std::string& publicKeyFile) { setLuaNoSideEffect(); #ifdef HAVE_DNSCRYPT unsigned char publicKey[DNSCRYPT_PROVIDER_PUBLIC_KEY_SIZE]; try { ifstream file(publicKeyFile); file.read((char*)&publicKey, sizeof(publicKey)); if (file.fail()) throw std::runtime_error("Invalid dnscrypt provider public key file " + publicKeyFile); file.close(); g_outputBuffer = "Provider fingerprint is: " + DNSCryptContext::getProviderFingerprint(publicKey) + "\n"; } catch (std::exception& e) { errlog(e.what()); g_outputBuffer = "Error: " + string(e.what()) + "\n"; } #else g_outputBuffer = "Error: DNSCrypt support is not enabled.\n"; #endif }); #ifdef HAVE_DNSCRYPT luaCtx.writeFunction("generateDNSCryptCertificate", [client](const std::string& providerPrivateKeyFile, const std::string& certificateFile, const std::string privateKeyFile, uint32_t serial, time_t begin, time_t end, boost::optional version) { setLuaNoSideEffect(); if (client) { return; } DNSCryptPrivateKey privateKey; DNSCryptCert cert; try { if (generateDNSCryptCertificate(providerPrivateKeyFile, serial, begin, end, version ? *version : DNSCryptExchangeVersion::VERSION1, cert, privateKey)) { privateKey.saveToFile(privateKeyFile); DNSCryptContext::saveCertFromFile(cert, certificateFile); } } catch (const std::exception& e) { errlog(e.what()); g_outputBuffer = "Error: " + string(e.what()) + "\n"; } }); #endif luaCtx.writeFunction("showPools", []() { setLuaNoSideEffect(); try { ostringstream ret; boost::format fmt("%1$-20.20s %|25t|%2$20s %|25t|%3$20s %|50t|%4%"); // 1 2 3 4 ret << (fmt % "Name" % "Cache" % "ServerPolicy" % "Servers") << endl; const auto localPools = g_pools.getCopy(); for (const auto& entry : localPools) { const string& name = entry.first; const std::shared_ptr pool = entry.second; string cache = pool->packetCache != nullptr ? pool->packetCache->toString() : ""; string policy = g_policy.getLocal()->getName(); if (pool->policy != nullptr) { policy = pool->policy->getName(); } string servers; const auto poolServers = pool->getServers(); for (const auto& server : *poolServers) { if (!servers.empty()) { servers += ", "; } if (!server.second->getName().empty()) { servers += server.second->getName(); servers += " "; } servers += server.second->remote.toStringWithPort(); } ret << (fmt % name % cache % policy % servers) << endl; } g_outputBuffer = ret.str(); } catch (std::exception& e) { g_outputBuffer = e.what(); throw; } }); luaCtx.writeFunction("getPool", [client](const string& poolName) { if (client) { return std::make_shared(); } auto localPools = g_pools.getCopy(); std::shared_ptr pool = createPoolIfNotExists(localPools, poolName); g_pools.setState(localPools); return pool; }); luaCtx.writeFunction("setVerboseHealthChecks", [](bool verbose) { g_verboseHealthChecks = verbose; }); luaCtx.writeFunction("setStaleCacheEntriesTTL", [](uint64_t ttl) { checkParameterBound("setStaleCacheEntriesTTL", ttl, std::numeric_limits::max()); g_staleCacheEntriesTTL = ttl; }); luaCtx.writeFunction("showBinds", []() { setLuaNoSideEffect(); try { ostringstream ret; boost::format fmt("%1$-3d %2$-20.20s %|35t|%3$-20.20s %|57t|%4%"); // 1 2 3 4 ret << (fmt % "#" % "Address" % "Protocol" % "Queries") << endl; size_t counter = 0; for (const auto& front : g_frontends) { ret << (fmt % counter % front->local.toStringWithPort() % front->getType() % front->queries) << endl; counter++; } g_outputBuffer = ret.str(); } catch (std::exception& e) { g_outputBuffer = e.what(); throw; } }); luaCtx.writeFunction("getBind", [](size_t num) { setLuaNoSideEffect(); ClientState* ret = nullptr; if (num < g_frontends.size()) { ret = g_frontends[num].get(); } return ret; }); luaCtx.writeFunction("getBindCount", []() { setLuaNoSideEffect(); return g_frontends.size(); }); luaCtx.writeFunction("help", [](boost::optional command) { setLuaNoSideEffect(); g_outputBuffer = ""; for (const auto& keyword : g_consoleKeywords) { if (!command) { g_outputBuffer += keyword.toString() + "\n"; } else if (keyword.name == command) { g_outputBuffer = keyword.toString() + "\n"; return; } } if (command) { g_outputBuffer = "Nothing found for " + *command + "\n"; } }); luaCtx.writeFunction("showVersion", []() { setLuaNoSideEffect(); g_outputBuffer = "dnsdist " + std::string(VERSION) + "\n"; }); luaCtx.writeFunction("showSecurityStatus", []() { setLuaNoSideEffect(); g_outputBuffer = std::to_string(g_stats.securityStatus) + "\n"; }); #ifdef HAVE_EBPF luaCtx.writeFunction("setDefaultBPFFilter", [](std::shared_ptr bpf) { if (g_configurationDone) { g_outputBuffer = "setDefaultBPFFilter() cannot be used at runtime!\n"; return; } g_defaultBPFFilter = bpf; }); luaCtx.writeFunction("registerDynBPFFilter", [](std::shared_ptr dbpf) { if (dbpf) { g_dynBPFFilters.push_back(dbpf); } }); luaCtx.writeFunction("unregisterDynBPFFilter", [](std::shared_ptr dbpf) { if (dbpf) { for (auto it = g_dynBPFFilters.begin(); it != g_dynBPFFilters.end(); it++) { if (*it == dbpf) { g_dynBPFFilters.erase(it); break; } } } }); luaCtx.writeFunction("addBPFFilterDynBlocks", [](const std::unordered_map& m, std::shared_ptr dynbpf, boost::optional seconds, boost::optional msg) { if (!dynbpf) { return; } setLuaSideEffect(); struct timespec until, now; clock_gettime(CLOCK_MONOTONIC, &now); until = now; int actualSeconds = seconds ? *seconds : 10; until.tv_sec += actualSeconds; for (const auto& capair : m) { if (dynbpf->block(capair.first, until)) { warnlog("Inserting eBPF dynamic block for %s for %d seconds: %s", capair.first.toString(), actualSeconds, msg ? *msg : ""); } } }); #endif /* HAVE_EBPF */ luaCtx.writeFunction()>("getStatisticsCounters", []() { setLuaNoSideEffect(); std::unordered_map res; for (const auto& entry : g_stats.entries) { if (const auto& val = boost::get(&entry.second)) res[entry.first] = (*val)->load(); } return res; }); luaCtx.writeFunction("includeDirectory", [&luaCtx](const std::string& dirname) { if (g_configurationDone) { errlog("includeDirectory() cannot be used at runtime!"); g_outputBuffer = "includeDirectory() cannot be used at runtime!\n"; return; } if (g_included) { errlog("includeDirectory() cannot be used recursively!"); g_outputBuffer = "includeDirectory() cannot be used recursively!\n"; return; } struct stat st; if (stat(dirname.c_str(), &st)) { errlog("The included directory %s does not exist!", dirname.c_str()); g_outputBuffer = "The included directory " + dirname + " does not exist!"; return; } if (!S_ISDIR(st.st_mode)) { errlog("The included directory %s is not a directory!", dirname.c_str()); g_outputBuffer = "The included directory " + dirname + " is not a directory!"; return; } DIR* dirp; struct dirent* ent; std::list files; if (!(dirp = opendir(dirname.c_str()))) { errlog("Error opening the included directory %s!", dirname.c_str()); g_outputBuffer = "Error opening the included directory " + dirname + "!"; return; } while ((ent = readdir(dirp)) != NULL) { if (ent->d_name[0] == '.') { continue; } if (boost::ends_with(ent->d_name, ".conf")) { std::ostringstream namebuf; namebuf << dirname.c_str() << "/" << ent->d_name; if (stat(namebuf.str().c_str(), &st) || !S_ISREG(st.st_mode)) { continue; } files.push_back(namebuf.str()); } } closedir(dirp); files.sort(); g_included = true; for (auto file = files.begin(); file != files.end(); ++file) { std::ifstream ifs(*file); if (!ifs) { warnlog("Unable to read configuration from '%s'", *file); } else { vinfolog("Read configuration from '%s'", *file); } try { luaCtx.executeCode(ifs); } catch (...) { g_included = false; throw; } luaCtx.executeCode(ifs); } g_included = false; }); luaCtx.writeFunction("setAPIWritable", [](bool writable, boost::optional apiConfigDir) { setLuaSideEffect(); g_apiReadWrite = writable; if (apiConfigDir) { if (!(*apiConfigDir).empty()) { g_apiConfigDirectory = *apiConfigDir; } else { errlog("The API configuration directory value cannot be empty!"); g_outputBuffer = "The API configuration directory value cannot be empty!"; } } }); luaCtx.writeFunction("setServFailWhenNoServer", [](bool servfail) { setLuaSideEffect(); g_servFailOnNoPolicy = servfail; }); luaCtx.writeFunction("setRoundRobinFailOnNoServer", [](bool fail) { setLuaSideEffect(); g_roundrobinFailOnNoServer = fail; }); luaCtx.writeFunction("setConsistentHashingBalancingFactor", [](double factor) { setLuaSideEffect(); if (factor >= 1.0) { g_consistentHashBalancingFactor = factor; } else { errlog("Invalid value passed to setConsistentHashingBalancingFactor()!"); g_outputBuffer = "Invalid value passed to setConsistentHashingBalancingFactor()!\n"; return; } }); luaCtx.writeFunction("setWeightedBalancingFactor", [](double factor) { setLuaSideEffect(); if (factor >= 1.0) { g_weightedBalancingFactor = factor; } else { errlog("Invalid value passed to setWeightedBalancingFactor()!"); g_outputBuffer = "Invalid value passed to setWeightedBalancingFactor()!\n"; return; } }); luaCtx.writeFunction("setRingBuffersSize", [client](size_t capacity, boost::optional numberOfShards) { setLuaSideEffect(); if (g_configurationDone) { errlog("setRingBuffersSize() cannot be used at runtime!"); g_outputBuffer = "setRingBuffersSize() cannot be used at runtime!\n"; return; } if (!client) { g_rings.setCapacity(capacity, numberOfShards ? *numberOfShards : 10); } else { g_rings.setCapacity(0, 1); } }); luaCtx.writeFunction("setRingBuffersLockRetries", [](size_t retries) { setLuaSideEffect(); g_rings.setNumberOfLockRetries(retries); }); luaCtx.writeFunction("setWHashedPertubation", [](uint64_t perturb) { setLuaSideEffect(); checkParameterBound("setWHashedPertubation", perturb, std::numeric_limits::max()); g_hashperturb = perturb; }); luaCtx.writeFunction("setTCPInternalPipeBufferSize", [](size_t size) { g_tcpInternalPipeBufferSize = size; }); luaCtx.writeFunction("snmpAgent", [client, configCheck](bool enableTraps, boost::optional daemonSocket) { if (client || configCheck) return; #ifdef HAVE_NET_SNMP if (g_configurationDone) { errlog("snmpAgent() cannot be used at runtime!"); g_outputBuffer = "snmpAgent() cannot be used at runtime!\n"; return; } if (g_snmpEnabled) { errlog("snmpAgent() cannot be used twice!"); g_outputBuffer = "snmpAgent() cannot be used twice!\n"; return; } g_snmpEnabled = true; g_snmpTrapsEnabled = enableTraps; g_snmpAgent = new DNSDistSNMPAgent("dnsdist", daemonSocket ? *daemonSocket : std::string()); #else errlog("NET SNMP support is required to use snmpAgent()"); g_outputBuffer = "NET SNMP support is required to use snmpAgent()\n"; #endif /* HAVE_NET_SNMP */ }); luaCtx.writeFunction("sendCustomTrap", [](const std::string& str) { if (g_snmpAgent && g_snmpTrapsEnabled) { g_snmpAgent->sendCustomTrap(str); } }); luaCtx.writeFunction("setServerPolicy", [](const ServerPolicy& policy) { setLuaSideEffect(); g_policy.setState(policy); }); luaCtx.writeFunction("setServerPolicyLua", [](string name, ServerPolicy::policyfunc_t policy) { setLuaSideEffect(); g_policy.setState(ServerPolicy{name, policy, true}); }); luaCtx.writeFunction("setServerPolicyLuaFFI", [](string name, ServerPolicy::ffipolicyfunc_t policy) { setLuaSideEffect(); auto pol = ServerPolicy(name, policy); g_policy.setState(std::move(pol)); }); luaCtx.writeFunction("setServerPolicyLuaFFIPerThread", [](string name, const std::string& policyCode) { setLuaSideEffect(); auto pol = ServerPolicy(name, policyCode); g_policy.setState(std::move(pol)); }); luaCtx.writeFunction("showServerPolicy", []() { setLuaSideEffect(); g_outputBuffer = g_policy.getLocal()->getName() + "\n"; }); luaCtx.writeFunction("setPoolServerPolicy", [](ServerPolicy policy, string pool) { setLuaSideEffect(); auto localPools = g_pools.getCopy(); setPoolPolicy(localPools, pool, std::make_shared(policy)); g_pools.setState(localPools); }); luaCtx.writeFunction("setPoolServerPolicyLua", [](string name, ServerPolicy::policyfunc_t policy, string pool) { setLuaSideEffect(); auto localPools = g_pools.getCopy(); setPoolPolicy(localPools, pool, std::make_shared(ServerPolicy{name, policy, true})); g_pools.setState(localPools); }); luaCtx.writeFunction("setPoolServerPolicyLuaFFI", [](string name, ServerPolicy::ffipolicyfunc_t policy, string pool) { setLuaSideEffect(); auto localPools = g_pools.getCopy(); setPoolPolicy(localPools, pool, std::make_shared(ServerPolicy{name, policy})); g_pools.setState(localPools); }); luaCtx.writeFunction("setPoolServerPolicyLuaFFIPerThread", [](string name, const std::string& policyCode, string pool) { setLuaSideEffect(); auto localPools = g_pools.getCopy(); setPoolPolicy(localPools, pool, std::make_shared(ServerPolicy{name, policyCode})); g_pools.setState(localPools); }); luaCtx.writeFunction("showPoolServerPolicy", [](string pool) { setLuaSideEffect(); auto localPools = g_pools.getCopy(); auto poolObj = getPool(localPools, pool); if (poolObj->policy == nullptr) { g_outputBuffer = g_policy.getLocal()->getName() + "\n"; } else { g_outputBuffer = poolObj->policy->getName() + "\n"; } }); luaCtx.writeFunction("setTCPDownstreamCleanupInterval", [](uint64_t interval) { setLuaSideEffect(); checkParameterBound("setTCPDownstreamCleanupInterval", interval); DownstreamTCPConnectionsManager::setCleanupInterval(interval); }); luaCtx.writeFunction("setDoHDownstreamCleanupInterval", [](uint64_t interval) { setLuaSideEffect(); checkParameterBound("setDoHDownstreamCleanupInterval", interval); setDoHDownstreamCleanupInterval(interval); }); luaCtx.writeFunction("setTCPDownstreamMaxIdleTime", [](uint64_t max) { setLuaSideEffect(); checkParameterBound("setTCPDownstreamMaxIdleTime", max); DownstreamTCPConnectionsManager::setMaxIdleTime(max); }); luaCtx.writeFunction("setDoHDownstreamMaxIdleTime", [](uint64_t max) { setLuaSideEffect(); checkParameterBound("setDoHDownstreamMaxIdleTime", max); setDoHDownstreamMaxIdleTime(max); }); luaCtx.writeFunction("setConsoleConnectionsLogging", [](bool enabled) { g_logConsoleConnections = enabled; }); luaCtx.writeFunction("setConsoleOutputMaxMsgSize", [](uint64_t size) { checkParameterBound("setConsoleOutputMaxMsgSize", size, std::numeric_limits::max()); g_consoleOutputMsgMaxSize = size; }); luaCtx.writeFunction("setProxyProtocolACL", [](boost::variant>> inp) { if (g_configurationDone) { errlog("setProxyProtocolACL() cannot be used at runtime!"); g_outputBuffer = "setProxyProtocolACL() cannot be used at runtime!\n"; return; } setLuaSideEffect(); NetmaskGroup nmg; if (auto str = boost::get(&inp)) { nmg.addMask(*str); } else { for (const auto& p : boost::get>>(inp)) { nmg.addMask(p.second); } } g_proxyProtocolACL = std::move(nmg); }); luaCtx.writeFunction("setProxyProtocolApplyACLToProxiedClients", [](bool apply) { if (g_configurationDone) { errlog("setProxyProtocolApplyACLToProxiedClients() cannot be used at runtime!"); g_outputBuffer = "setProxyProtocolApplyACLToProxiedClients() cannot be used at runtime!\n"; return; } setLuaSideEffect(); g_applyACLToProxiedClients = apply; }); luaCtx.writeFunction("setProxyProtocolMaximumPayloadSize", [](size_t size) { if (g_configurationDone) { errlog("setProxyProtocolMaximumPayloadSize() cannot be used at runtime!"); g_outputBuffer = "setProxyProtocolMaximumPayloadSize() cannot be used at runtime!\n"; return; } setLuaSideEffect(); g_proxyProtocolMaximumSize = std::max(static_cast(16), size); }); luaCtx.writeFunction("setUDPMultipleMessagesVectorSize", [](size_t vSize) { if (g_configurationDone) { errlog("setUDPMultipleMessagesVectorSize() cannot be used at runtime!"); g_outputBuffer = "setUDPMultipleMessagesVectorSize() cannot be used at runtime!\n"; return; } #if defined(HAVE_RECVMMSG) && defined(HAVE_SENDMMSG) && defined(MSG_WAITFORONE) setLuaSideEffect(); g_udpVectorSize = vSize; #else errlog("recvmmsg() support is not available!"); g_outputBuffer = "recvmmsg support is not available!\n"; #endif }); luaCtx.writeFunction("setAddEDNSToSelfGeneratedResponses", [](bool add) { g_addEDNSToSelfGeneratedResponses = add; }); luaCtx.writeFunction("setPayloadSizeOnSelfGeneratedAnswers", [](uint16_t payloadSize) { if (payloadSize < 512) { warnlog("setPayloadSizeOnSelfGeneratedAnswers() is set too low, using 512 instead!"); g_outputBuffer = "setPayloadSizeOnSelfGeneratedAnswers() is set too low, using 512 instead!"; payloadSize = 512; } if (payloadSize > s_udpIncomingBufferSize) { warnlog("setPayloadSizeOnSelfGeneratedAnswers() is set too high, capping to %d instead!", s_udpIncomingBufferSize); g_outputBuffer = "setPayloadSizeOnSelfGeneratedAnswers() is set too high, capping to " + std::to_string(s_udpIncomingBufferSize) + " instead"; payloadSize = s_udpIncomingBufferSize; } g_PayloadSizeSelfGenAnswers = payloadSize; }); luaCtx.writeFunction("setSecurityPollSuffix", [](const std::string& suffix) { if (g_configurationDone) { g_outputBuffer = "setSecurityPollSuffix() cannot be used at runtime!\n"; return; } g_secPollSuffix = suffix; }); luaCtx.writeFunction("setSecurityPollInterval", [](time_t newInterval) { if (newInterval <= 0) { warnlog("setSecurityPollInterval() should be > 0, skipping"); g_outputBuffer = "setSecurityPollInterval() should be > 0, skipping"; } g_secPollInterval = newInterval; }); luaCtx.writeFunction("setSyslogFacility", [](boost::variant facility) { setLuaSideEffect(); if (g_configurationDone) { g_outputBuffer = "setSyslogFacility cannot be used at runtime!\n"; return; } if (facility.type() == typeid(std::string)) { static std::map const facilities = { {"local0", LOG_LOCAL0}, {"log_local0", LOG_LOCAL0}, {"local1", LOG_LOCAL1}, {"log_local1", LOG_LOCAL1}, {"local2", LOG_LOCAL2}, {"log_local2", LOG_LOCAL2}, {"local3", LOG_LOCAL3}, {"log_local3", LOG_LOCAL3}, {"local4", LOG_LOCAL4}, {"log_local4", LOG_LOCAL4}, {"local5", LOG_LOCAL5}, {"log_local5", LOG_LOCAL5}, {"local6", LOG_LOCAL6}, {"log_local6", LOG_LOCAL6}, {"local7", LOG_LOCAL7}, {"log_local7", LOG_LOCAL7}, /* most of these likely make very little sense for dnsdist, but why not? */ {"kern", LOG_KERN}, {"log_kern", LOG_KERN}, {"user", LOG_USER}, {"log_user", LOG_USER}, {"mail", LOG_MAIL}, {"log_mail", LOG_MAIL}, {"daemon", LOG_DAEMON}, {"log_daemon", LOG_DAEMON}, {"auth", LOG_AUTH}, {"log_auth", LOG_AUTH}, {"syslog", LOG_SYSLOG}, {"log_syslog", LOG_SYSLOG}, {"lpr", LOG_LPR}, {"log_lpr", LOG_LPR}, {"news", LOG_NEWS}, {"log_news", LOG_NEWS}, {"uucp", LOG_UUCP}, {"log_uucp", LOG_UUCP}, {"cron", LOG_CRON}, {"log_cron", LOG_CRON}, {"authpriv", LOG_AUTHPRIV}, {"log_authpriv", LOG_AUTHPRIV}, {"ftp", LOG_FTP}, {"log_ftp", LOG_FTP}}; auto facilityStr = boost::get(facility); toLowerInPlace(facilityStr); auto it = facilities.find(facilityStr); if (it == facilities.end()) { g_outputBuffer = "Unknown facility '" + facilityStr + "' passed to setSyslogFacility()!\n"; return; } setSyslogFacility(it->second); } else { setSyslogFacility(boost::get(facility)); } }); luaCtx.writeFunction("addDOHLocal", [client](const std::string& addr, boost::optional>>> certFiles, boost::optional>>> keyFiles, boost::optional>>> urls, boost::optional vars) { if (client) { return; } #ifdef HAVE_DNS_OVER_HTTPS setLuaSideEffect(); if (g_configurationDone) { g_outputBuffer = "addDOHLocal cannot be used at runtime!\n"; return; } auto frontend = std::make_shared(); if (certFiles && !certFiles->empty() && keyFiles && !keyFiles->empty()) { if (!loadTLSCertificateAndKeys("addDOHLocal", frontend->d_tlsConfig.d_certKeyPairs, *certFiles, *keyFiles)) { return; } frontend->d_local = ComboAddress(addr, 443); } else { frontend->d_local = ComboAddress(addr, 80); infolog("No certificate provided for DoH endpoint %s, running in DNS over HTTP mode instead of DNS over HTTPS", frontend->d_local.toStringWithPort()); } if (urls) { if (urls->type() == typeid(std::string)) { frontend->d_urls.push_back(boost::get(*urls)); } else if (urls->type() == typeid(std::vector>)) { auto urlsVect = boost::get>>(*urls); for (const auto& p : urlsVect) { frontend->d_urls.push_back(p.second); } } } else { frontend->d_urls = {"/dns-query"}; } bool reusePort = false; int tcpFastOpenQueueSize = 0; int tcpListenQueueSize = 0; size_t maxInFlightQueriesPerConn = 0; size_t tcpMaxConcurrentConnections = 0; std::string interface; std::set cpus; if (vars) { parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConnections); if (vars->count("idleTimeout")) { frontend->d_idleTimeout = boost::get((*vars)["idleTimeout"]); } if (vars->count("serverTokens")) { frontend->d_serverTokens = boost::get((*vars)["serverTokens"]); } if (vars->count("customResponseHeaders")) { for (auto const& headerMap : boost::get>((*vars).at("customResponseHeaders"))) { frontend->d_customResponseHeaders.emplace_back(boost::to_lower_copy(headerMap.first), headerMap.second); } } if (vars->count("sendCacheControlHeaders")) { frontend->d_sendCacheControlHeaders = boost::get((*vars)["sendCacheControlHeaders"]); } if (vars->count("trustForwardedForHeader")) { frontend->d_trustForwardedForHeader = boost::get((*vars)["trustForwardedForHeader"]); } if (vars->count("internalPipeBufferSize")) { frontend->d_internalPipeBufferSize = boost::get((*vars)["internalPipeBufferSize"]); } if (vars->count("exactPathMatching")) { frontend->d_exactPathMatching = boost::get((*vars)["exactPathMatching"]); } parseTLSConfig(frontend->d_tlsConfig, "addDOHLocal", vars); } g_dohlocals.push_back(frontend); auto cs = std::make_unique(frontend->d_local, true, reusePort, tcpFastOpenQueueSize, interface, cpus); cs->dohFrontend = frontend; if (tcpListenQueueSize > 0) { cs->tcpListenQueueSize = tcpListenQueueSize; } if (tcpMaxConcurrentConnections > 0) { cs->d_tcpConcurrentConnectionsLimit = tcpMaxConcurrentConnections; } g_frontends.push_back(std::move(cs)); #else throw std::runtime_error("addDOHLocal() called but DNS over HTTPS support is not present!"); #endif }); luaCtx.writeFunction("showDOHFrontends", []() { #ifdef HAVE_DNS_OVER_HTTPS setLuaNoSideEffect(); try { ostringstream ret; boost::format fmt("%-3d %-20.20s %-15d %-15d %-15d %-15d %-15d %-15d %-15d %-15d %-15d %-15d %-15d %-15d"); ret << (fmt % "#" % "Address" % "HTTP" % "HTTP/1" % "HTTP/2" % "GET" % "POST" % "Bad" % "Errors" % "Redirects" % "Valid" % "# ticket keys" % "Rotation delay" % "Next rotation") << endl; size_t counter = 0; for (const auto& ctx : g_dohlocals) { ret << (fmt % counter % ctx->d_local.toStringWithPort() % ctx->d_httpconnects % ctx->d_http1Stats.d_nbQueries % ctx->d_http2Stats.d_nbQueries % ctx->d_getqueries % ctx->d_postqueries % ctx->d_badrequests % ctx->d_errorresponses % ctx->d_redirectresponses % ctx->d_validresponses % ctx->getTicketsKeysCount() % ctx->getTicketsKeyRotationDelay() % ctx->getNextTicketsKeyRotation()) << endl; counter++; } g_outputBuffer = ret.str(); } catch (const std::exception& e) { g_outputBuffer = e.what(); throw; } #else g_outputBuffer = "DNS over HTTPS support is not present!\n"; #endif }); luaCtx.writeFunction("showDOHResponseCodes", []() { #ifdef HAVE_DNS_OVER_HTTPS setLuaNoSideEffect(); try { ostringstream ret; boost::format fmt("%-3d %-20.20s %-15d %-15d %-15d %-15d %-15d %-15d"); g_outputBuffer = "\n- HTTP/1:\n\n"; ret << (fmt % "#" % "Address" % "200" % "400" % "403" % "500" % "502" % "Others") << endl; size_t counter = 0; for (const auto& ctx : g_dohlocals) { ret << (fmt % counter % ctx->d_local.toStringWithPort() % ctx->d_http1Stats.d_nb200Responses % ctx->d_http1Stats.d_nb400Responses % ctx->d_http1Stats.d_nb403Responses % ctx->d_http1Stats.d_nb500Responses % ctx->d_http1Stats.d_nb502Responses % ctx->d_http1Stats.d_nbOtherResponses) << endl; counter++; } g_outputBuffer += ret.str(); ret.str(""); g_outputBuffer += "\n- HTTP/2:\n\n"; ret << (fmt % "#" % "Address" % "200" % "400" % "403" % "500" % "502" % "Others") << endl; counter = 0; for (const auto& ctx : g_dohlocals) { ret << (fmt % counter % ctx->d_local.toStringWithPort() % ctx->d_http2Stats.d_nb200Responses % ctx->d_http2Stats.d_nb400Responses % ctx->d_http2Stats.d_nb403Responses % ctx->d_http2Stats.d_nb500Responses % ctx->d_http2Stats.d_nb502Responses % ctx->d_http2Stats.d_nbOtherResponses) << endl; counter++; } g_outputBuffer += ret.str(); } catch (const std::exception& e) { g_outputBuffer = e.what(); throw; } #else g_outputBuffer = "DNS over HTTPS support is not present!\n"; #endif }); luaCtx.writeFunction("getDOHFrontend", [client](size_t index) { std::shared_ptr result = nullptr; if (client) { return result; } #ifdef HAVE_DNS_OVER_HTTPS setLuaNoSideEffect(); try { if (index < g_dohlocals.size()) { result = g_dohlocals.at(index); } else { errlog("Error: trying to get DOH frontend with index %zu but we only have %zu frontend(s)\n", index, g_dohlocals.size()); g_outputBuffer = "Error: trying to get DOH frontend with index " + std::to_string(index) + " but we only have " + std::to_string(g_dohlocals.size()) + " frontend(s)\n"; } } catch (const std::exception& e) { g_outputBuffer = "Error while trying to get DOH frontend with index " + std::to_string(index) + ": " + string(e.what()) + "\n"; errlog("Error while trying to get DOH frontend with index %zu: %s\n", index, string(e.what())); } #else g_outputBuffer="DNS over HTTPS support is not present!\n"; #endif return result; }); luaCtx.writeFunction("getDOHFrontendCount", []() { setLuaNoSideEffect(); return g_dohlocals.size(); }); luaCtx.registerFunction::*)()>("reloadCertificates", [](std::shared_ptr frontend) { if (frontend != nullptr) { frontend->reloadCertificates(); } }); luaCtx.registerFunction::*)(boost::variant>> certFiles, boost::variant>> keyFiles)>("loadNewCertificatesAndKeys", [](std::shared_ptr frontend, boost::variant>> certFiles, boost::variant>> keyFiles) { #ifdef HAVE_DNS_OVER_HTTPS if (frontend != nullptr) { if (loadTLSCertificateAndKeys("DOHFrontend::loadNewCertificatesAndKeys", frontend->d_tlsConfig.d_certKeyPairs, certFiles, keyFiles)) { frontend->reloadCertificates(); } } #endif }); luaCtx.registerFunction::*)()>("rotateTicketsKey", [](std::shared_ptr frontend) { if (frontend != nullptr) { frontend->rotateTicketsKey(time(nullptr)); } }); luaCtx.registerFunction::*)(const std::string&)>("loadTicketsKeys", [](std::shared_ptr frontend, const std::string& file) { if (frontend != nullptr) { frontend->loadTicketsKeys(file); } }); luaCtx.registerFunction::*)(const std::map>&)>("setResponsesMap", [](std::shared_ptr frontend, const std::map>& map) { if (frontend != nullptr) { auto newMap = std::make_shared>>(); newMap->reserve(map.size()); for (const auto& entry : map) { newMap->push_back(entry.second); } frontend->d_responsesMap = std::move(newMap); } }); luaCtx.registerFunction::*)() const>("getAddressAndPort", [](const std::shared_ptr& frontend) { if (frontend == nullptr) { return std::string(); } return frontend->d_local.toStringWithPort(); }); luaCtx.writeFunction("addTLSLocal", [client](const std::string& addr, boost::variant>> certFiles, boost::variant>> keyFiles, boost::optional vars) { if (client) { return; } #ifdef HAVE_DNS_OVER_TLS setLuaSideEffect(); if (g_configurationDone) { g_outputBuffer = "addTLSLocal cannot be used at runtime!\n"; return; } shared_ptr frontend = std::make_shared(); if (!loadTLSCertificateAndKeys("addTLSLocal", frontend->d_tlsConfig.d_certKeyPairs, certFiles, keyFiles)) { return; } bool reusePort = false; int tcpFastOpenQueueSize = 0; int tcpListenQueueSize = 0; size_t maxInFlightQueriesPerConn = 0; size_t tcpMaxConcurrentConns = 0; std::string interface; std::set cpus; if (vars) { parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConns); if (vars->count("provider")) { frontend->d_provider = boost::get((*vars)["provider"]); boost::algorithm::to_lower(frontend->d_provider); } parseTLSConfig(frontend->d_tlsConfig, "addTLSLocal", vars); } try { frontend->d_addr = ComboAddress(addr, 853); if (!frontend->d_provider.empty()) { vinfolog("Loading TLS provider '%s'", frontend->d_provider); } else { #ifdef HAVE_LIBSSL vinfolog("Loading default TLS provider 'openssl'"); #else vinfolog("Loading default TLS provider 'gnutls'"); #endif } // only works pre-startup, so no sync necessary auto cs = std::make_unique(frontend->d_addr, true, reusePort, tcpFastOpenQueueSize, interface, cpus); cs->tlsFrontend = frontend; if (tcpListenQueueSize > 0) { cs->tcpListenQueueSize = tcpListenQueueSize; } if (maxInFlightQueriesPerConn > 0) { cs->d_maxInFlightQueriesPerConn = maxInFlightQueriesPerConn; } if (tcpMaxConcurrentConns > 0) { cs->d_tcpConcurrentConnectionsLimit = tcpMaxConcurrentConns; } g_tlslocals.push_back(cs->tlsFrontend); g_frontends.push_back(std::move(cs)); } catch (const std::exception& e) { g_outputBuffer = "Error: " + string(e.what()) + "\n"; } #else throw std::runtime_error("addTLSLocal() called but DNS over TLS support is not present!"); #endif }); luaCtx.writeFunction("showTLSContexts", []() { #ifdef HAVE_DNS_OVER_TLS setLuaNoSideEffect(); try { ostringstream ret; boost::format fmt("%1$-3d %2$-20.20s %|25t|%3$-14d %|40t|%4$-14d %|54t|%5$-21.21s"); // 1 2 3 4 5 ret << (fmt % "#" % "Address" % "# ticket keys" % "Rotation delay" % "Next rotation") << endl; size_t counter = 0; for (const auto& ctx : g_tlslocals) { ret << (fmt % counter % ctx->d_addr.toStringWithPort() % ctx->getTicketsKeysCount() % ctx->getTicketsKeyRotationDelay() % ctx->getNextTicketsKeyRotation()) << endl; counter++; } g_outputBuffer = ret.str(); } catch (const std::exception& e) { g_outputBuffer = e.what(); throw; } #else g_outputBuffer = "DNS over TLS support is not present!\n"; #endif }); luaCtx.writeFunction("getTLSContext", [](size_t index) { std::shared_ptr result = nullptr; #ifdef HAVE_DNS_OVER_TLS setLuaNoSideEffect(); try { if (index < g_tlslocals.size()) { result = g_tlslocals.at(index)->getContext(); } else { errlog("Error: trying to get TLS context with index %zu but we only have %zu context(s)\n", index, g_tlslocals.size()); g_outputBuffer = "Error: trying to get TLS context with index " + std::to_string(index) + " but we only have " + std::to_string(g_tlslocals.size()) + " context(s)\n"; } } catch (const std::exception& e) { g_outputBuffer = "Error while trying to get TLS context with index " + std::to_string(index) + ": " + string(e.what()) + "\n"; errlog("Error while trying to get TLS context with index %zu: %s\n", index, string(e.what())); } #else g_outputBuffer="DNS over TLS support is not present!\n"; #endif return result; }); luaCtx.writeFunction("getTLSFrontend", [](size_t index) { std::shared_ptr result = nullptr; #ifdef HAVE_DNS_OVER_TLS setLuaNoSideEffect(); try { if (index < g_tlslocals.size()) { result = g_tlslocals.at(index); } else { errlog("Error: trying to get TLS frontend with index %zu but we only have %zu frontends\n", index, g_tlslocals.size()); g_outputBuffer = "Error: trying to get TLS frontend with index " + std::to_string(index) + " but we only have " + std::to_string(g_tlslocals.size()) + " frontend(s)\n"; } } catch (const std::exception& e) { g_outputBuffer = "Error while trying to get TLS frontend with index " + std::to_string(index) + ": " + string(e.what()) + "\n"; errlog("Error while trying to get TLS frontend with index %zu: %s\n", index, string(e.what())); } #else g_outputBuffer="DNS over TLS support is not present!\n"; #endif return result; }); luaCtx.writeFunction("getTLSFrontendCount", []() { setLuaNoSideEffect(); return g_tlslocals.size(); }); luaCtx.registerFunction::*)()>("rotateTicketsKey", [](std::shared_ptr& ctx) { if (ctx != nullptr) { ctx->rotateTicketsKey(time(nullptr)); } }); luaCtx.registerFunction::*)(const std::string&)>("loadTicketsKeys", [](std::shared_ptr& ctx, const std::string& file) { if (ctx != nullptr) { ctx->loadTicketsKeys(file); } }); luaCtx.registerFunction::*)() const>("getAddressAndPort", [](const std::shared_ptr& frontend) { if (frontend == nullptr) { return std::string(); } return frontend->d_addr.toStringWithPort(); }); luaCtx.registerFunction::*)()>("rotateTicketsKey", [](std::shared_ptr& frontend) { if (frontend == nullptr) { return; } auto ctx = frontend->getContext(); if (ctx) { ctx->rotateTicketsKey(time(nullptr)); } }); luaCtx.registerFunction::*)(const std::string&)>("loadTicketsKeys", [](std::shared_ptr& frontend, const std::string& file) { if (frontend == nullptr) { return; } auto ctx = frontend->getContext(); if (ctx) { ctx->loadTicketsKeys(file); } }); luaCtx.registerFunction::*)()>("reloadCertificates", [](std::shared_ptr& frontend) { if (frontend == nullptr) { return; } frontend->setupTLS(); }); luaCtx.registerFunction::*)(boost::variant>> certFiles, boost::variant>> keyFiles)>("loadNewCertificatesAndKeys", [](std::shared_ptr& frontend, boost::variant>> certFiles, boost::variant>> keyFiles) { #ifdef HAVE_DNS_OVER_TLS if (loadTLSCertificateAndKeys("TLSFrontend::loadNewCertificatesAndKeys", frontend->d_tlsConfig.d_certKeyPairs, certFiles, keyFiles)) { frontend->setupTLS(); } #endif }); luaCtx.writeFunction("reloadAllCertificates", []() { for (auto& frontend : g_frontends) { if (!frontend) { continue; } try { #ifdef HAVE_DNSCRYPT if (frontend->dnscryptCtx) { frontend->dnscryptCtx->reloadCertificates(); } #endif /* HAVE_DNSCRYPT */ #ifdef HAVE_DNS_OVER_TLS if (frontend->tlsFrontend) { frontend->tlsFrontend->setupTLS(); } #endif /* HAVE_DNS_OVER_TLS */ #ifdef HAVE_DNS_OVER_HTTPS if (frontend->dohFrontend) { frontend->dohFrontend->reloadCertificates(); } #endif /* HAVE_DNS_OVER_HTTPS */ } catch (const std::exception& e) { errlog("Error reloading certificates for frontend %s: %s", frontend->local.toStringWithPort(), e.what()); } } }); luaCtx.writeFunction("setAllowEmptyResponse", [](bool allow) { g_allowEmptyResponse = allow; }); luaCtx.writeFunction("setDropEmptyQueries", [](bool drop) { extern bool g_dropEmptyQueries; g_dropEmptyQueries = drop; }); #if defined(HAVE_LIBSSL) && defined(HAVE_OCSP_BASIC_SIGN) luaCtx.writeFunction("generateOCSPResponse", [client](const std::string& certFile, const std::string& caCert, const std::string& caKey, const std::string& outFile, int ndays, int nmin) { if (client) { return; } libssl_generate_ocsp_response(certFile, caCert, caKey, outFile, ndays, nmin); }); #endif /* HAVE_LIBSSL && HAVE_OCSP_BASIC_SIGN*/ luaCtx.writeFunction("addCapabilitiesToRetain", [](boost::variant> caps) { setLuaSideEffect(); if (g_configurationDone) { g_outputBuffer = "addCapabilitiesToRetain cannot be used at runtime!\n"; return; } if (caps.type() == typeid(std::string)) { g_capabilitiesToRetain.insert(boost::get(caps)); } else if (caps.type() == typeid(std::map)) { for (const auto& cap : boost::get>(caps)) { g_capabilitiesToRetain.insert(cap.second); } } }); luaCtx.writeFunction("setUDPSocketBufferSizes", [client](uint64_t recv, uint64_t snd) { if (client) { return; } checkParameterBound("setUDPSocketBufferSizes", recv, std::numeric_limits::max()); checkParameterBound("setUDPSocketBufferSizes", snd, std::numeric_limits::max()); setLuaSideEffect(); if (g_configurationDone) { g_outputBuffer = "setUDPSocketBufferSizes cannot be used at runtime!\n"; return; } g_socketUDPSendBuffer = snd; g_socketUDPRecvBuffer = recv; }); } vector> setupLua(LuaContext& luaCtx, bool client, bool configCheck, const std::string& config) { // this needs to exist only during the parsing of the configuration // and cannot be captured by lambdas g_launchWork = std::vector>(); setupLuaActions(luaCtx); setupLuaConfig(luaCtx, client, configCheck); setupLuaBindings(luaCtx, client); setupLuaBindingsDNSCrypt(luaCtx); setupLuaBindingsDNSQuestion(luaCtx); setupLuaBindingsKVS(luaCtx, client); setupLuaBindingsPacketCache(luaCtx, client); setupLuaBindingsProtoBuf(luaCtx, client, configCheck); setupLuaInspection(luaCtx); setupLuaRules(luaCtx); setupLuaVars(luaCtx); setupLuaWeb(luaCtx); #ifdef LUAJIT_VERSION luaCtx.executeCode(getLuaFFIWrappers()); #endif std::ifstream ifs(config); if (!ifs) warnlog("Unable to read configuration from '%s'", config); else vinfolog("Read configuration from '%s'", config); luaCtx.executeCode(ifs); auto ret = *g_launchWork; g_launchWork = boost::none; return ret; }