#include <functional>
#include <map>
+#include <memory>
+#include <optional>
#include <string>
+#include "credentials.hh"
#include "dnsdist-query-count.hh"
#include "dnsdist-rule-chains.hh"
#include "iputils.hh"
{
std::set<std::string> d_capabilitiesToRetain;
std::vector<uint32_t> d_tcpFastOpenKey;
- ComboAddress d_consoleServerAddress{"127.0.0.1:5199"};
- std::string d_consoleKey;
#ifdef __linux__
// On Linux this gives us 128k pending queries (default is 8192 queries),
// which should be enough to deal with huge spikes
a RCU-like mechanism */
struct RuntimeConfiguration
{
- // ca tient pas la route: meilleure option: stocker un type plus opaque dans la configuration (dnsdist::rules::RuleChains) et
- // laisser le soin a dnsdist::rules de le gerer
- /* std::vector<rules::RuleAction> d_cacheMissRuleActions;
- std::vector<rules::ResponseRuleAction> d_respruleactions;
- std::vector<rules::ResponseRuleAction> d_cachehitrespruleactions;
- std::vector<rules::ResponseRuleAction> d_selfansweredrespruleactions;
- std::vector<rules::ResponseRuleAction> d_cacheInsertedRespRuleActions;
- std::vector<rules::ResponseRuleAction> d_XFRRespRuleActions;
- */
rules::RuleChains d_ruleChains;
servers_t d_backends;
std::map<std::string, std::shared_ptr<ServerPool>> d_pools;
+ std::shared_ptr<const CredentialsHolder> d_webPassword;
+ std::shared_ptr<const CredentialsHolder> d_webAPIKey;
+ std::optional<std::unordered_map<std::string, std::string>> d_webCustomHeaders;
std::shared_ptr<ServerPolicy> d_lbPolicy;
NetmaskGroup d_ACL;
NetmaskGroup d_proxyProtocolACL;
NetmaskGroup d_consoleACL;
+ NetmaskGroup d_webServerACL;
+ std::optional<ComboAddress> d_webServerAddress{std::nullopt};
dnsdist::QueryCount::Configuration d_queryCountConfig;
+ ComboAddress d_consoleServerAddress{"127.0.0.1:5199"};
+ std::string d_consoleKey;
std::string d_secPollSuffix{"secpoll.powerdns.com."};
std::string d_apiConfigDirectory;
uint64_t d_dynBlocksPurgeInterval{60};
uint16_t d_tlsSessionCacheSessionValidity{600};
uint16_t d_tlsSessionCacheMaxSessionsPerBackend{20};
DNSAction::Action d_dynBlockAction{DNSAction::Action::Drop};
+ bool d_apiRequiresAuthentication{true};
+ bool d_dashboardRequiresAuthentication{true};
+ bool d_statsRequireAuthentication{true};
bool d_truncateTC{false};
bool d_fixupCase{false};
bool d_queryCountEnabled{false};
static ConsoleCommandResult sendMessageToServer(int fileDesc, const std::string& line, dnsdist::crypto::authenticated::Nonce& readingNonce, dnsdist::crypto::authenticated::Nonce& writingNonce, const bool outputEmptyLine)
{
- const auto& consoleKey = dnsdist::configuration::getImmutableConfiguration().d_consoleKey;
+ const auto& consoleKey = dnsdist::configuration::getCurrentRuntimeConfiguration().d_consoleKey;
string msg = dnsdist::crypto::authenticated::encryptSym(line, consoleKey, writingNonce);
const auto msgLen = msg.length();
if (msgLen > std::numeric_limits<uint32_t>::max()) {
{
void doClient(const std::string& command)
{
- const auto consoleKey = dnsdist::configuration::getImmutableConfiguration().d_consoleKey;
- const auto server = dnsdist::configuration::getImmutableConfiguration().d_consoleServerAddress;
+ const auto consoleKey = dnsdist::configuration::getCurrentRuntimeConfiguration().d_consoleKey;
+ const auto server = dnsdist::configuration::getCurrentRuntimeConfiguration().d_consoleServerAddress;
if (!dnsdist::crypto::authenticated::isValidKey(consoleKey)) {
cerr << "The currently configured console key is not valid, please configure a valid key using the setKey() directive" << endl;
return;
setTCPNoDelay(conn.getFD());
- const auto& consoleKey = dnsdist::configuration::getImmutableConfiguration().d_consoleKey;
+ const auto consoleKey = dnsdist::configuration::getCurrentRuntimeConfiguration().d_consoleKey;
dnsdist::crypto::authenticated::Nonce theirs;
dnsdist::crypto::authenticated::Nonce ours;
dnsdist::crypto::authenticated::Nonce readingNonce;
}
}
-// NOLINTNEXTLINE(performance-unnecessary-value-param): this is thread
-void controlThread(std::shared_ptr<Socket>&& acceptFD, ComboAddress local)
+void controlThread(Socket&& acceptFD)
{
try {
setThreadName("dnsdist/control");
+ const ComboAddress local = dnsdist::configuration::getCurrentRuntimeConfiguration().d_consoleServerAddress;
s_connManager.setMaxConcurrentConnections(dnsdist::configuration::getImmutableConfiguration().d_consoleMaxConcurrentConnections);
ComboAddress client;
int sock{-1};
infolog("Accepting control connections on %s", local.toStringWithPort());
- while ((sock = SAccept(acceptFD->getHandle(), client)) >= 0) {
- const auto& consoleKey = dnsdist::configuration::getImmutableConfiguration().d_consoleKey;
+ while ((sock = SAccept(acceptFD.getHandle(), client)) >= 0) {
+ const auto& consoleKey = dnsdist::configuration::getCurrentRuntimeConfiguration().d_consoleKey;
FDWrapper socket(sock);
if (!dnsdist::crypto::authenticated::isValidKey(consoleKey)) {
vinfolog("Control connection from %s dropped because we don't have a valid key configured, please configure one using setKey()", client.toStringWithPort());
const std::vector<std::pair<timeval, std::string>>& getConfigurationDelta();
void doClient(const std::string& command);
void doConsole();
-void controlThread(std::shared_ptr<Socket>&& acceptFD, ComboAddress local);
+void controlThread(Socket&& acceptFD);
void clearHistory();
#ifndef DISABLE_COMPLETION
#include "dnsdist-lua.hh"
#include "dnsdist-web.hh"
+namespace dnsdist::webserver
+{
void registerWebHandler(const std::string& endpoint, std::function<void(const YaHTTP::Request&, YaHTTP::Response&)> handler, bool isLua);
+}
void setupLuaWeb(LuaContext& luaCtx)
{
#ifndef DISABLE_LUA_WEB_HANDLERS
luaCtx.writeFunction("registerWebHandler", [](const std::string& path, std::function<void(const YaHTTP::Request*, YaHTTP::Response*)> handler) {
/* LuaWrapper does a copy for objects passed by reference, so we pass a pointer */
- registerWebHandler(path, [handler](const YaHTTP::Request& req, YaHTTP::Response& resp) { handler(&req, &resp); }, true);
+ dnsdist::webserver::registerWebHandler(path, [handler](const YaHTTP::Request& req, YaHTTP::Response& resp) { handler(&req, &resp); }, true);
});
luaCtx.registerMember<std::string(YaHTTP::Request::*)>("path", [](const YaHTTP::Request& req) -> std::string { return req.url.path; }, [](YaHTTP::Request& req, const std::string& path) { (void) path; });
using std::thread;
-static boost::optional<std::vector<std::function<void(void)>>> g_launchWork = boost::none;
+static std::optional<std::vector<std::function<void(void)>>> s_launchWork{std::nullopt};
static boost::tribool s_noLuaSideEffect;
}
if (ret->connected) {
- if (g_launchWork) {
- g_launchWork->push_back([ret]() {
+ if (s_launchWork) {
+ s_launchWork->push_back([ret]() {
ret->start();
});
}
return;
}
- 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 thr(dnsdistWebserverThread, sock, local);
+ dnsdist::configuration::updateRuntimeConfiguration([local](dnsdist::configuration::RuntimeConfiguration& config) {
+ config.d_webServerAddress = local;
+ });
+
+ if (dnsdist::configuration::isConfigurationDone()) {
+ try {
+ auto sock = Socket(local.sin4.sin_family, SOCK_STREAM, 0);
+ sock.bind(local, true);
+ sock.listen(5);
+ thread thr(dnsdist::webserver::WebserverThread, std::move(sock));
thr.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());
}
}
- 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 LuaAssociativeTable<boost::variant<bool, std::string, LuaAssociativeTable<std::string>>> webserveropts_t;
+ using webserveropts_t = LuaAssociativeTable<boost::variant<bool, std::string, LuaAssociativeTable<std::string>>>;
luaCtx.writeFunction("setWebserverConfig", [](boost::optional<webserveropts_t> vars) {
setLuaSideEffect();
return;
}
- bool hashPlaintextCredentials = false;
- getOptionalValue<bool>(vars, "hashPlaintextCredentials", hashPlaintextCredentials);
-
- std::string password;
- std::string apiKey;
- std::string acl;
- LuaAssociativeTable<std::string> headers;
- bool statsRequireAuthentication{true};
- bool apiRequiresAuthentication{true};
- bool dashboardRequiresAuthentication{true};
- int maxConcurrentConnections = 0;
-
- if (getOptionalValue<std::string>(vars, "password", password) > 0) {
- auto holder = make_unique<CredentialsHolder>(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.");
+ dnsdist::configuration::updateRuntimeConfiguration([&vars](dnsdist::configuration::RuntimeConfiguration& config) {
+ std::string password;
+ std::string apiKey;
+ std::string acl;
+ LuaAssociativeTable<std::string> headers;
+ bool statsRequireAuthentication{true};
+ bool apiRequiresAuthentication{true};
+ bool dashboardRequiresAuthentication{true};
+ bool hashPlaintextCredentials = false;
+ getOptionalValue<bool>(vars, "hashPlaintextCredentials", hashPlaintextCredentials);
+
+ if (getOptionalValue<std::string>(vars, "password", password) > 0) {
+ auto holder = std::make_shared<CredentialsHolder>(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.");
+ }
+ config.d_webPassword = std::move(holder);
}
- setWebserverPassword(std::move(holder));
- }
-
- if (getOptionalValue<std::string>(vars, "apiKey", apiKey) > 0) {
- auto holder = make_unique<CredentialsHolder>(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.");
+ if (getOptionalValue<std::string>(vars, "apiKey", apiKey) > 0) {
+ auto holder = std::make_shared<CredentialsHolder>(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.");
+ }
+ config.d_webAPIKey = std::move(holder);
}
- setWebserverAPIKey(std::move(holder));
- }
-
- if (getOptionalValue<std::string>(vars, "acl", acl) > 0) {
- setWebserverACL(acl);
- }
+ if (getOptionalValue<std::string>(vars, "acl", acl) > 0) {
+ NetmaskGroup ACLnmg;
+ ACLnmg.toMasks(acl);
+ config.d_webServerACL = std::move(ACLnmg);
+ }
- if (getOptionalValue<decltype(headers)>(vars, "customHeaders", headers) > 0) {
- setWebserverCustomHeaders(headers);
- }
+ if (getOptionalValue<decltype(headers)>(vars, "customHeaders", headers) > 0) {
+ config.d_webCustomHeaders = std::move(headers);
+ }
- if (getOptionalValue<bool>(vars, "statsRequireAuthentication", statsRequireAuthentication) > 0) {
- setWebserverStatsRequireAuthentication(statsRequireAuthentication);
- }
+ if (getOptionalValue<bool>(vars, "statsRequireAuthentication", statsRequireAuthentication) > 0) {
+ config.d_statsRequireAuthentication = statsRequireAuthentication;
+ }
- if (getOptionalValue<bool>(vars, "apiRequiresAuthentication", apiRequiresAuthentication) > 0) {
- setWebserverAPIRequiresAuthentication(apiRequiresAuthentication);
- }
+ if (getOptionalValue<bool>(vars, "apiRequiresAuthentication", apiRequiresAuthentication) > 0) {
+ config.d_apiRequiresAuthentication = apiRequiresAuthentication;
+ }
- if (getOptionalValue<bool>(vars, "dashboardRequiresAuthentication", dashboardRequiresAuthentication) > 0) {
- setWebserverDashboardRequiresAuthentication(dashboardRequiresAuthentication);
- }
+ if (getOptionalValue<bool>(vars, "dashboardRequiresAuthentication", dashboardRequiresAuthentication) > 0) {
+ config.d_dashboardRequiresAuthentication = dashboardRequiresAuthentication;
+ }
+ });
+ int maxConcurrentConnections = 0;
if (getOptionalIntegerValue("setWebserverConfig", vars, "maxConcurrentConnections", maxConcurrentConnections) > 0) {
- setWebserverMaxConcurrentConnections(maxConcurrentConnections);
+ dnsdist::webserver::setMaxConcurrentConnections(maxConcurrentConnections);
}
});
luaCtx.writeFunction("showWebserverConfig", []() {
setLuaNoSideEffect();
- return getWebserverConfig();
+ return dnsdist::webserver::getConfig();
});
luaCtx.writeFunction("hashPassword", [](const std::string& password, boost::optional<uint64_t> workFactor) {
ComboAddress local(str, 5199);
if (client || configCheck) {
- dnsdist::configuration::updateImmutableConfiguration([&local](dnsdist::configuration::Configuration& config) {
- config.d_consoleServerAddress = local;
- });
return;
}
- dnsdist::configuration::updateRuntimeConfiguration([](dnsdist::configuration::RuntimeConfiguration& config) {
+ dnsdist::configuration::updateRuntimeConfiguration([local](dnsdist::configuration::RuntimeConfiguration& config) {
+ config.d_consoleServerAddress = local;
config.d_consoleEnabled = true;
});
#if defined(HAVE_LIBSODIUM) || defined(HAVE_LIBCRYPTO)
- if (dnsdist::configuration::isConfigurationDone() && dnsdist::configuration::getImmutableConfiguration().d_consoleKey.empty()) {
+ if (dnsdist::configuration::isConfigurationDone() && dnsdist::configuration::getCurrentRuntimeConfiguration().d_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 {
- auto sock = std::make_shared<Socket>(local.sin4.sin_family, SOCK_STREAM, 0);
- sock->bind(local, true);
- sock->listen(5);
- auto launch = [sock = std::move(sock), local]() {
- std::thread consoleControlThread(dnsdist::console::controlThread, std::move(sock), local);
+ if (dnsdist::configuration::isConfigurationDone()) {
+ try {
+ auto sock = Socket(local.sin4.sin_family, SOCK_STREAM, 0);
+ sock.bind(local, true);
+ sock.listen(5);
+ std::thread consoleControlThread(dnsdist::console::controlThread, std::move(sock));
consoleControlThread.detach();
- };
- if (g_launchWork) {
- g_launchWork->emplace_back(std::move(launch));
}
- else {
- launch();
+ catch (const std::exception& exp) {
+ g_outputBuffer = "Unable to bind to control socket on " + local.toStringWithPort() + ": " + exp.what();
+ errlog("Unable to bind to control socket on %s: %s", local.toStringWithPort(), exp.what());
}
}
- 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) {
});
luaCtx.writeFunction("setKey", [](const std::string& key) {
- if (!dnsdist::configuration::isConfigurationDone() && !dnsdist::configuration::getImmutableConfiguration().d_consoleKey.empty()) { // this makes sure the commandline -k key prevails over dnsdist.conf
+ if (!dnsdist::configuration::isConfigurationDone() && !dnsdist::configuration::getCurrentRuntimeConfiguration().d_consoleKey.empty()) { // this makes sure the commandline -k key prevails over dnsdist.conf
return; // but later setKeys() trump the -k value again
}
#if !defined(HAVE_LIBSODIUM) && !defined(HAVE_LIBCRYPTO)
return;
}
- dnsdist::configuration::updateImmutableConfiguration([&newKey](dnsdist::configuration::Configuration& config) {
+ dnsdist::configuration::updateRuntimeConfiguration([&newKey](dnsdist::configuration::RuntimeConfiguration& config) {
config.d_consoleKey = std::move(newKey);
});
});
setLuaNoSideEffect();
#if defined(HAVE_LIBSODIUM) || defined(HAVE_LIBCRYPTO)
try {
- const auto& consoleKey = dnsdist::configuration::getImmutableConfiguration().d_consoleKey;
+ const auto& consoleKey = dnsdist::configuration::getCurrentRuntimeConfiguration().d_consoleKey;
string testmsg;
if (optTestMsg) {
{
// this needs to exist only during the parsing of the configuration
// and cannot be captured by lambdas
- g_launchWork = std::vector<std::function<void(void)>>();
+ s_launchWork = std::vector<std::function<void(void)>>();
setupLuaActions(luaCtx);
setupLuaConfig(luaCtx, client, configCheck);
luaCtx.executeCode(ifs);
- auto ret = *g_launchWork;
- g_launchWork = boost::none;
+ auto ret = std::move(*s_launchWork);
+ s_launchWork = std::nullopt;
+
return ret;
}
void parseRuleParams(boost::optional<luaruleparams_t>& params, boost::uuids::uuid& uuid, std::string& name, uint64_t& creationOrder);
void checkParameterBound(const std::string& parameter, uint64_t value, size_t max = std::numeric_limits<uint16_t>::max());
-vector<std::function<void(void)>> setupLua(LuaContext& luaCtx, bool client, bool configCheck, const std::string& config);
+std::vector<std::function<void(void)>> setupLua(LuaContext& luaCtx, bool client, bool configCheck, const std::string& config);
void setupLuaActions(LuaContext& luaCtx);
void setupLuaBindings(LuaContext& luaCtx, bool client, bool configCheck);
void setupLuaBindingsDNSCrypt(LuaContext& luaCtx, bool client);
if (itp.second) {
g_stats.entries.write_lock()->emplace_back(Stats::EntryPair{name, &(*customCounters)[name].d_value});
dnsdist::prometheus::PrometheusMetricDefinition def{name, type, description, finalCustomName};
- addMetricDefinition(def);
+ dnsdist::webserver::addMetricDefinition(def);
}
}
else if (type == "gauge") {
if (itp.second) {
g_stats.entries.write_lock()->emplace_back(Stats::EntryPair{name, &(*customGauges)[name].d_value});
dnsdist::prometheus::PrometheusMetricDefinition def{name, type, description, finalCustomName};
- addMetricDefinition(def);
+ dnsdist::webserver::addMetricDefinition(def);
}
}
else {
#include "threadname.hh"
#include "sstuff.hh"
-struct WebserverConfig
-{
- WebserverConfig()
- {
- acl.toMasks("127.0.0.1, ::1");
- }
-
- NetmaskGroup acl;
- std::unique_ptr<CredentialsHolder> password;
- std::unique_ptr<CredentialsHolder> apiKey;
- boost::optional<std::unordered_map<std::string, std::string>> customHeaders;
- bool apiRequiresAuthentication{true};
- bool dashboardRequiresAuthentication{true};
- bool statsRequireAuthentication{true};
-};
-
-LockGuarded<WebserverConfig> g_webserverConfig;
-
-static ConcurrentConnectionManager s_connManager(100);
-
-std::string getWebserverConfig()
-{
- ostringstream out;
-
- {
- auto config = g_webserverConfig.lock();
- out << "Current web server configuration:" << endl;
- out << "ACL: " << config->acl.toString() << endl;
- out << "Custom headers: ";
- if (config->customHeaders) {
- out << endl;
- for (const auto& header : *config->customHeaders) {
- out << " - " << header.first << ": " << header.second << endl;
- }
- }
- else {
- out << "None" << endl;
- }
- out << "API requires authentication: " << (config->apiRequiresAuthentication ? "yes" : "no") << endl;
- out << "Dashboard requires authentication: " << (config->dashboardRequiresAuthentication ? "yes" : "no") << endl;
- out << "Statistics require authentication: " << (config->statsRequireAuthentication ? "yes" : "no") << endl;
- out << "Password: " << (config->password ? "set" : "unset") << endl;
- out << "API key: " << (config->apiKey ? "set" : "unset") << endl;
- }
- {
- const auto& config = dnsdist::configuration::getCurrentRuntimeConfiguration();
- out << "API writable: " << (config.d_apiReadWrite ? "yes" : "no") << endl;
- out << "API configuration directory: " << config.d_apiConfigDirectory << endl;
- out << "Maximum concurrent connections: " << s_connManager.getMaxConcurrentConnections() << endl;
- }
-
- return out.str();
-}
-
-class WebClientConnection
-{
-public:
- WebClientConnection(const ComboAddress& client, int socketDesc) :
- d_client(client), d_socket(socketDesc)
- {
- if (!s_connManager.registerConnection()) {
- throw std::runtime_error("Too many concurrent web client connections");
- }
- }
- WebClientConnection(WebClientConnection&& rhs) noexcept :
- d_client(rhs.d_client), d_socket(std::move(rhs.d_socket))
- {
- }
- WebClientConnection(const WebClientConnection&) = delete;
- WebClientConnection& operator=(const WebClientConnection&) = delete;
- WebClientConnection& operator=(WebClientConnection&& rhs) noexcept
- {
- d_client = rhs.d_client;
- d_socket = std::move(rhs.d_socket);
- return *this;
- }
-
- ~WebClientConnection()
- {
- if (d_socket.getHandle() != -1) {
- s_connManager.releaseConnection();
- }
- }
-
- [[nodiscard]] const Socket& getSocket() const
- {
- return d_socket;
- }
-
- [[nodiscard]] const ComboAddress& getClient() const
- {
- return d_client;
- }
-
-private:
- ComboAddress d_client;
- Socket d_socket;
-};
-
#ifndef DISABLE_PROMETHEUS
static MetricDefinitionStorage s_metricDefinitions;
};
#endif /* DISABLE_PROMETHEUS */
+namespace dnsdist::webserver
+{
+static ConcurrentConnectionManager s_connManager(100);
+
+std::string getConfig()
+{
+ ostringstream out;
+
+ {
+ const auto& config = dnsdist::configuration::getCurrentRuntimeConfiguration();
+ out << "Current web server configuration:" << endl;
+ out << "ACL: " << config.d_webServerACL.toString() << endl;
+ out << "Custom headers: ";
+ if (config.d_webCustomHeaders) {
+ out << endl;
+ for (const auto& header : *config.d_webCustomHeaders) {
+ out << " - " << header.first << ": " << header.second << endl;
+ }
+ }
+ else {
+ out << "None" << endl;
+ }
+ out << "API requires authentication: " << (config.d_apiRequiresAuthentication ? "yes" : "no") << endl;
+ out << "Dashboard requires authentication: " << (config.d_dashboardRequiresAuthentication ? "yes" : "no") << endl;
+ out << "Statistics require authentication: " << (config.d_statsRequireAuthentication ? "yes" : "no") << endl;
+ out << "Password: " << (config.d_webPassword ? "set" : "unset") << endl;
+ out << "API key: " << (config.d_webAPIKey ? "set" : "unset") << endl;
+ out << "API writable: " << (config.d_apiReadWrite ? "yes" : "no") << endl;
+ out << "API configuration directory: " << config.d_apiConfigDirectory << endl;
+ out << "Maximum concurrent connections: " << s_connManager.getMaxConcurrentConnections() << endl;
+ }
+
+ return out.str();
+}
+
+class WebClientConnection
+{
+public:
+ WebClientConnection(const ComboAddress& client, int socketDesc) :
+ d_client(client), d_socket(socketDesc)
+ {
+ if (!s_connManager.registerConnection()) {
+ throw std::runtime_error("Too many concurrent web client connections");
+ }
+ }
+ WebClientConnection(WebClientConnection&& rhs) noexcept :
+ d_client(rhs.d_client), d_socket(std::move(rhs.d_socket))
+ {
+ }
+ WebClientConnection(const WebClientConnection&) = delete;
+ WebClientConnection& operator=(const WebClientConnection&) = delete;
+ WebClientConnection& operator=(WebClientConnection&& rhs) noexcept
+ {
+ d_client = rhs.d_client;
+ d_socket = std::move(rhs.d_socket);
+ return *this;
+ }
+
+ ~WebClientConnection()
+ {
+ if (d_socket.getHandle() != -1) {
+ s_connManager.releaseConnection();
+ }
+ }
+
+ [[nodiscard]] const Socket& getSocket() const
+ {
+ return d_socket;
+ }
+
+ [[nodiscard]] const ComboAddress& getClient() const
+ {
+ return d_client;
+ }
+
+private:
+ ComboAddress d_client;
+ Socket d_socket;
+};
+
bool addMetricDefinition(const dnsdist::prometheus::PrometheusMetricDefinition& def)
{
#ifndef DISABLE_PROMETHEUS
}
#endif /* DISABLE_WEB_CONFIG */
-static bool checkAPIKey(const YaHTTP::Request& req, const std::unique_ptr<CredentialsHolder>& apiKey)
+static bool checkAPIKey(const YaHTTP::Request& req, const std::shared_ptr<const CredentialsHolder>& apiKey)
{
if (!apiKey) {
return false;
return false;
}
-static bool checkWebPassword(const YaHTTP::Request& req, const std::unique_ptr<CredentialsHolder>& password, bool dashboardRequiresAuthentication)
+static bool checkWebPassword(const YaHTTP::Request& req, const std::shared_ptr<const CredentialsHolder>& password, bool dashboardRequiresAuthentication)
{
if (!dashboardRequiresAuthentication) {
return true;
static bool handleAuthorization(const YaHTTP::Request& req)
{
- auto config = g_webserverConfig.lock();
+ const auto& config = dnsdist::configuration::getCurrentRuntimeConfiguration();
if (isAStatsRequest(req)) {
- if (config->statsRequireAuthentication) {
+ if (config.d_statsRequireAuthentication) {
/* Access to the stats is allowed for both API and Web users */
- return checkAPIKey(req, config->apiKey) || checkWebPassword(req, config->password, config->dashboardRequiresAuthentication);
+ return checkAPIKey(req, config.d_webAPIKey) || checkWebPassword(req, config.d_webPassword, config.d_dashboardRequiresAuthentication);
}
return true;
}
if (isAnAPIRequest(req)) {
/* Access to the API requires a valid API key */
- if (!config->apiRequiresAuthentication || checkAPIKey(req, config->apiKey)) {
+ if (!config.d_apiRequiresAuthentication || checkAPIKey(req, config.d_webAPIKey)) {
return true;
}
- return isAnAPIRequestAllowedWithWebAuth(req) && checkWebPassword(req, config->password, config->dashboardRequiresAuthentication);
+ return isAnAPIRequestAllowedWithWebAuth(req) && checkWebPassword(req, config.d_webPassword, config.d_dashboardRequiresAuthentication);
}
- return checkWebPassword(req, config->password, config->dashboardRequiresAuthentication);
+ return checkWebPassword(req, config.d_webPassword, config.d_dashboardRequiresAuthentication);
}
static bool isMethodAllowed(const YaHTTP::Request& req)
static bool isClientAllowedByACL(const ComboAddress& remote)
{
- return g_webserverConfig.lock()->acl.match(remote);
+ const auto& config = dnsdist::configuration::getCurrentRuntimeConfiguration();
+ return config.d_webServerACL.match(remote);
}
static void handleCORS(const YaHTTP::Request& req, YaHTTP::Response& resp)
}
}
-static void addSecurityHeaders(YaHTTP::Response& resp, const boost::optional<std::unordered_map<std::string, std::string>>& customHeaders)
+static void addSecurityHeaders(YaHTTP::Response& resp, const std::optional<std::unordered_map<std::string, std::string>>& customHeaders)
{
static const std::vector<std::pair<std::string, std::string>> headers = {
{"X-Content-Type-Options", "nosniff"},
}
}
-static void addCustomHeaders(YaHTTP::Response& resp, const boost::optional<std::unordered_map<std::string, std::string>>& customHeaders)
+static void addCustomHeaders(YaHTTP::Response& resp, const std::optional<std::unordered_map<std::string, std::string>>& customHeaders)
{
if (!customHeaders) {
return;
std::vector<std::pair<std::string, configentry_t>> configEntries{
{"acl", runtimeConfiguration.d_ACL.toString()},
{"allow-empty-response", runtimeConfiguration.d_allowEmptyResponse},
- {"control-socket", immutableConfig.d_consoleServerAddress.toStringWithPort()},
+ {"control-socket", runtimeConfiguration.d_consoleServerAddress.toStringWithPort()},
{"ecs-override", runtimeConfiguration.d_ecsOverride},
{"ecs-source-prefix-v4", static_cast<double>(runtimeConfiguration.d_ECSSourcePrefixV4)},
{"ecs-source-prefix-v6", static_cast<double>(runtimeConfiguration.d_ECSSourcePrefixV6)},
resp.version = req.version;
{
- auto config = g_webserverConfig.lock();
-
- addCustomHeaders(resp, config->customHeaders);
- addSecurityHeaders(resp, config->customHeaders);
+ const auto& config = dnsdist::configuration::getCurrentRuntimeConfiguration();
+ addCustomHeaders(resp, config.d_webCustomHeaders);
+ addSecurityHeaders(resp, config.d_webCustomHeaders);
}
/* indicate that the connection will be closed after completion of the response */
resp.headers["Connection"] = "close";
}
}
-void setWebserverAPIKey(std::unique_ptr<CredentialsHolder>&& apiKey)
-{
- auto config = g_webserverConfig.lock();
-
- if (apiKey) {
- config->apiKey = std::move(apiKey);
- }
- else {
- config->apiKey.reset();
- }
-}
-
-void setWebserverPassword(std::unique_ptr<CredentialsHolder>&& password)
-{
- g_webserverConfig.lock()->password = std::move(password);
-}
-
-void setWebserverACL(const std::string& acl)
-{
- NetmaskGroup newACL;
- newACL.toMasks(acl);
-
- g_webserverConfig.lock()->acl = std::move(newACL);
-}
-
-void setWebserverCustomHeaders(const boost::optional<std::unordered_map<std::string, std::string>>& customHeaders)
-{
- g_webserverConfig.lock()->customHeaders = customHeaders;
-}
-
-void setWebserverStatsRequireAuthentication(bool require)
-{
- g_webserverConfig.lock()->statsRequireAuthentication = require;
-}
-
-void setWebserverAPIRequiresAuthentication(bool require)
-{
- g_webserverConfig.lock()->apiRequiresAuthentication = require;
-}
-
-void setWebserverDashboardRequiresAuthentication(bool require)
-{
- g_webserverConfig.lock()->dashboardRequiresAuthentication = require;
-}
-
-void setWebserverMaxConcurrentConnections(size_t max)
+void setMaxConcurrentConnections(size_t max)
{
s_connManager.setMaxConcurrentConnections(max);
}
-void dnsdistWebserverThread(int sock, const ComboAddress& local)
+void WebserverThread(Socket sock)
{
setThreadName("dnsdist/webserv");
+ const auto local = *dnsdist::configuration::getCurrentRuntimeConfiguration().d_webServerAddress;
infolog("Webserver launched on %s", local.toStringWithPort());
{
- auto config = g_webserverConfig.lock();
- if (!config->password && config->dashboardRequiresAuthentication) {
+ const auto& config = dnsdist::configuration::getCurrentRuntimeConfiguration();
+ if (!config.d_webPassword && config.d_dashboardRequiresAuthentication) {
warnlog("Webserver launched on %s without a password set!", local.toStringWithPort());
}
}
for (;;) {
try {
ComboAddress remote(local);
- int fileDesc = SAccept(sock, remote);
+ int fileDesc = SAccept(sock.getHandle(), remote);
if (!isClientAllowedByACL(remote)) {
vinfolog("Connection to webserver from client %s is not allowed, closing", remote.toStringWithPort());
}
}
}
+}
#include "credentials.hh"
#include "dnsdist-prometheus.hh"
-void setWebserverAPIKey(std::unique_ptr<CredentialsHolder>&& apiKey);
-void setWebserverPassword(std::unique_ptr<CredentialsHolder>&& password);
-void setWebserverACL(const std::string& acl);
-void setWebserverCustomHeaders(const boost::optional<std::unordered_map<std::string, std::string> >& customHeaders);
-void setWebserverAPIRequiresAuthentication(bool);
-void setWebserverDashboardRequiresAuthentication(bool);
-void setWebserverStatsRequireAuthentication(bool);
-void setWebserverMaxConcurrentConnections(size_t);
-
-void dnsdistWebserverThread(int sock, const ComboAddress& local);
-
+namespace dnsdist::webserver
+{
+void WebserverThread(Socket sock);
+void setMaxConcurrentConnections(size_t max);
void registerBuiltInWebHandlers();
void clearWebHandlers();
-
-std::string getWebserverConfig();
-
+std::string getConfig();
bool addMetricDefinition(const dnsdist::prometheus::PrometheusMetricDefinition& def);
+}
// NOLINTNEXTLINE(concurrency-mt-unsafe): only one thread at this point
exit(EXIT_FAILURE);
}
- dnsdist::configuration::updateImmutableConfiguration([&consoleKey](dnsdist::configuration::Configuration& config) {
+ dnsdist::configuration::updateRuntimeConfiguration([&consoleKey](dnsdist::configuration::RuntimeConfiguration& config) {
config.d_consoleKey = std::move(consoleKey);
});
}
}
}
+struct ListeningSockets
+{
+ Socket d_consoleSocket{-1};
+ Socket d_webServerSocket{-1};
+};
+
+static ListeningSockets initListeningSockets()
+{
+ ListeningSockets result;
+ const auto& currentConfig = dnsdist::configuration::getCurrentRuntimeConfiguration();
+
+ if (currentConfig.d_consoleEnabled) {
+ const auto& local = currentConfig.d_consoleServerAddress;
+ try {
+ result.d_consoleSocket = Socket(local.sin4.sin_family, SOCK_STREAM, 0);
+ result.d_consoleSocket.bind(local, true);
+ result.d_consoleSocket.listen(5);
+ }
+ catch (const std::exception& exp) {
+ errlog("Unable to bind to control socket on %s: %s", local.toStringWithPort(), exp.what());
+ }
+ }
+
+ if (currentConfig.d_webServerAddress) {
+ const auto& local = *currentConfig.d_webServerAddress;
+ try {
+ result.d_webServerSocket = Socket(local.sin4.sin_family, SOCK_STREAM, 0);
+ result.d_webServerSocket.bind(local, true);
+ result.d_webServerSocket.listen(5);
+ }
+ catch (const std::exception& exp) {
+ errlog("Unable to bind to web server socket on %s: %s", local.toStringWithPort(), exp.what());
+ }
+ }
+
+ return result;
+}
+
int main(int argc, char** argv)
{
try {
if (cmdLine.beClient || !cmdLine.command.empty()) {
setupLua(*(g_lua.lock()), true, false, cmdLine.config);
if (clientAddress != ComboAddress()) {
- dnsdist::configuration::updateImmutableConfiguration([&clientAddress](dnsdist::configuration::Configuration& config) {
+ dnsdist::configuration::updateRuntimeConfiguration([&clientAddress](dnsdist::configuration::RuntimeConfiguration& config) {
config.d_consoleServerAddress = clientAddress;
});
}
acl.addMask(addr);
}
}
- });
-
- dnsdist::configuration::updateRuntimeConfiguration([](dnsdist::configuration::RuntimeConfiguration& config) {
for (const auto& mask : {"127.0.0.1/8", "::1/128"}) {
config.d_consoleACL.addMask(mask);
}
+ config.d_webServerACL.toMasks("127.0.0.1, ::1");
});
- registerBuiltInWebHandlers();
+ dnsdist::webserver::registerBuiltInWebHandlers();
if (cmdLine.checkConfig) {
setupLua(*(g_lua.lock()), false, true, cmdLine.config);
infolog("Console ACL allowing connections from: %s", acls.c_str());
}
+ auto listeningSockets = initListeningSockets();
+
#if defined(HAVE_LIBSODIUM) || defined(HAVE_LIBCRYPTO)
- if (dnsdist::configuration::getCurrentRuntimeConfiguration().d_consoleEnabled && dnsdist::configuration::getImmutableConfiguration().d_consoleKey.empty()) {
+ if (dnsdist::configuration::getCurrentRuntimeConfiguration().d_consoleEnabled && dnsdist::configuration::getCurrentRuntimeConfiguration().d_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
initDoHWorkers();
#endif
+ if (dnsdist::configuration::getCurrentRuntimeConfiguration().d_consoleEnabled) {
+ std::thread consoleControlThread(dnsdist::console::controlThread, std::move(listeningSockets.d_consoleSocket));
+ consoleControlThread.detach();
+ }
+ if (dnsdist::configuration::getCurrentRuntimeConfiguration().d_webServerAddress) {
+ std::thread webServerThread(dnsdist::webserver::WebserverThread, std::move(listeningSockets.d_webServerSocket));
+ webServerThread.detach();
+ }
+
for (auto& todoItem : todo) {
todoItem();
}
#include "dnsparser.hh"
#include "dnswriter.hh"
-bool addMetricDefinition(const dnsdist::prometheus::PrometheusMetricDefinition& def)
+bool dnsdist::webserver::addMetricDefinition(const dnsdist::prometheus::PrometheusMetricDefinition& def)
{
return true;
}