2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of version 2 of the GNU General Public License as
7 * published by the Free Software Foundation.
9 * In addition, for the avoidance of any doubt, permission is granted to
10 * link this program with OpenSSL and to (re)distribute the binaries
11 * produced as the result of such linking.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
27 #if defined (__OpenBSD__) || defined(__NetBSD__)
28 #include <readline/readline.h>
29 #include <readline/history.h>
31 #include <editline/readline.h>
34 #include "ext/json11/json11.hpp"
38 #include "dnsdist-console.hh"
39 #include "sodcrypto.hh"
40 #include "threadname.hh"
42 GlobalStateHolder
<NetmaskGroup
> g_consoleACL
;
43 vector
<pair
<struct timeval
, string
> > g_confDelta
;
44 std::string g_consoleKey
;
45 bool g_logConsoleConnections
{true};
46 bool g_consoleEnabled
{false};
47 uint32_t g_consoleOutputMsgMaxSize
{10000000};
49 // MUST BE CALLED UNDER A LOCK - right now the LuaLock
50 static void feedConfigDelta(const std::string
& line
)
55 gettimeofday(&now
, 0);
56 g_confDelta
.push_back({now
,line
});
59 static string
historyFile(const bool &ignoreHOME
= false)
64 struct passwd
*result
;
66 getpwuid_r(geteuid(), &pwd
, buf
, sizeof(buf
), &result
);
68 const char *homedir
= getenv("HOME");
70 ret
= string(pwd
.pw_dir
);
71 if (homedir
&& !ignoreHOME
) // $HOME overrides what the OS tells us
72 ret
= string(homedir
);
74 ret
= "."; // CWD if nothing works..
75 ret
.append("/.dnsdist_history");
79 static bool getMsgLen32(int fd
, uint32_t* len
)
83 size_t ret
= readn2(fd
, &raw
, sizeof raw
);
87 if(*len
> g_consoleOutputMsgMaxSize
)
95 static bool putMsgLen32(int fd
, uint32_t len
)
98 uint32_t raw
= htonl(len
);
99 size_t ret
= writen2(fd
, &raw
, sizeof raw
);
100 return ret
==sizeof raw
;
106 static bool sendMessageToServer(int fd
, const std::string
& line
, SodiumNonce
& readingNonce
, SodiumNonce
& writingNonce
, const bool outputEmptyLine
)
108 string msg
= sodEncryptSym(line
, g_consoleKey
, writingNonce
);
109 const auto msgLen
= msg
.length();
110 if (msgLen
> std::numeric_limits
<uint32_t>::max()) {
111 cout
<< "Encrypted message is too long to be sent to the server, "<< std::to_string(msgLen
) << " > " << std::numeric_limits
<uint32_t>::max() << endl
;
115 putMsgLen32(fd
, static_cast<uint32_t>(msgLen
));
122 if(!getMsgLen32(fd
, &len
)) {
123 cout
<< "Connection closed by the server." << endl
;
128 if (outputEmptyLine
) {
135 boost::scoped_array
<char> resp(new char[len
]);
136 readn2(fd
, resp
.get(), len
);
137 msg
.assign(resp
.get(), len
);
138 msg
= sodDecryptSym(msg
, g_consoleKey
, readingNonce
);
145 void doClient(ComboAddress server
, const std::string
& command
)
147 if (!sodIsValidKey(g_consoleKey
)) {
148 cerr
<< "The currently configured console key is not valid, please configure a valid key using the setKey() directive" << endl
;
153 cout
<<"Connecting to "<<server
.toStringWithPort()<<endl
;
156 int fd
=socket(server
.sin4
.sin_family
, SOCK_STREAM
, 0);
158 cerr
<<"Unable to connect to "<<server
.toStringWithPort()<<endl
;
161 SConnect(fd
, server
);
163 SodiumNonce theirs
, ours
, readingNonce
, writingNonce
;
166 writen2(fd
, (const char*)ours
.value
, sizeof(ours
.value
));
167 readn2(fd
, (char*)theirs
.value
, sizeof(theirs
.value
));
168 readingNonce
.merge(ours
, theirs
);
169 writingNonce
.merge(theirs
, ours
);
171 /* try sending an empty message, the server should send an empty
172 one back. If it closes the connection instead, we are probably
173 having a key mismatch issue. */
174 if (!sendMessageToServer(fd
, "", readingNonce
, writingNonce
, false)) {
175 cerr
<<"The server closed the connection right away, likely indicating a key mismatch. Please check your setKey() directive."<<endl
;
180 if (!command
.empty()) {
181 sendMessageToServer(fd
, command
, readingNonce
, writingNonce
, false);
187 string histfile
= historyFile();
190 ifstream
history(histfile
);
192 while(getline(history
, line
))
193 add_history(line
.c_str());
195 ofstream
history(histfile
, std::ios_base::app
);
198 char* sline
= readline("> ");
199 rl_bind_key('\t',rl_complete
);
204 if(!line
.empty() && line
!= lastline
) {
206 history
<< sline
<<endl
;
214 if(line
=="help" || line
=="?")
217 /* no need to send an empty line to the server */
221 if (!sendMessageToServer(fd
, line
, readingNonce
, writingNonce
, true)) {
230 string histfile
= historyFile(true);
233 ifstream
history(histfile
);
235 while(getline(history
, line
))
236 add_history(line
.c_str());
238 ofstream
history(histfile
, std::ios_base::app
);
241 char* sline
= readline("> ");
242 rl_bind_key('\t',rl_complete
);
247 if(!line
.empty() && line
!= lastline
) {
249 history
<< sline
<<endl
;
257 if(line
=="help" || line
=="?")
262 bool withReturn
=true;
265 std::lock_guard
<std::mutex
> lock(g_luamutex
);
266 g_outputBuffer
.clear();
267 resetLuaSideEffect();
268 auto ret
=g_lua
.executeCode
<
272 shared_ptr
<DownstreamState
>,
274 std::unordered_map
<string
, double>
277 >(withReturn
? ("return "+line
) : line
);
279 if (const auto dsValue
= boost::get
<shared_ptr
<DownstreamState
>>(&*ret
)) {
281 cout
<<(*dsValue
)->getName()<<endl
;
284 else if (const auto csValue
= boost::get
<ClientState
*>(&*ret
)) {
286 cout
<<(*csValue
)->local
.toStringWithPort()<<endl
;
289 else if (const auto strValue
= boost::get
<string
>(&*ret
)) {
290 cout
<<*strValue
<<endl
;
292 else if(const auto um
= boost::get
<std::unordered_map
<string
, double> >(&*ret
)) {
293 using namespace json11
;
295 for(const auto& v
: *um
)
298 cout
<<out
.dump()<<endl
;
302 cout
<< g_outputBuffer
;
303 if(!getLuaNoSideEffect())
304 feedConfigDelta(line
);
306 catch(const LuaContext::SyntaxErrorException
&) {
314 catch(const LuaContext::WrongTypeException
& e
) {
315 std::cerr
<<"Command returned an object we can't print: "<<std::string(e
.what())<<std::endl
;
316 // tried to return something we don't understand
318 catch(const LuaContext::ExecutionErrorException
& e
) {
319 if(!strcmp(e
.what(),"invalid key to 'next'"))
320 std::cerr
<<"Error parsing parameters, did you forget parameter name?";
322 std::cerr
<< e
.what();
324 std::rethrow_if_nested(e
);
326 std::cerr
<< std::endl
;
327 } catch(const std::exception
& ne
) {
328 // ne is the exception that was thrown from inside the lambda
329 std::cerr
<< ": " << ne
.what() << std::endl
;
331 catch(const PDNSException
& ne
) {
332 // ne is the exception that was thrown from inside the lambda
333 std::cerr
<< ": " << ne
.reason
<< std::endl
;
336 catch(const std::exception
& e
) {
337 std::cerr
<< e
.what() << std::endl
;
341 /**** CARGO CULT CODE AHEAD ****/
342 const std::vector
<ConsoleKeyword
> g_consoleKeywords
{
343 /* keyword, function, parameters, description */
344 { "addACL", true, "netmask", "add to the ACL set who can use this server" },
345 { "addAction", true, "DNS rule, DNS action [, {uuid=\"UUID\"}]", "add a rule" },
346 { "addConsoleACL", true, "netmask", "add a netmask to the console ACL" },
347 { "addDNSCryptBind", true, "\"127.0.0.1:8443\", \"provider name\", \"/path/to/resolver.cert\", \"/path/to/resolver.key\", {reusePort=false, tcpFastOpenSize=0, interface=\"\", cpus={}}", "listen to incoming DNSCrypt queries on 127.0.0.1 port 8443, with a provider name of `provider name`, using a resolver certificate and associated key stored respectively in the `resolver.cert` and `resolver.key` files. The fifth optional parameter is a table of parameters" },
348 { "addDynBlocks", true, "addresses, message[, seconds[, action]]", "block the set of addresses with message `msg`, for `seconds` seconds (10 by default), applying `action` (default to the one set with `setDynBlocksAction()`)" },
349 { "addLocal", true, "addr [, {doTCP=true, reusePort=false, tcpFastOpenSize=0, interface=\"\", cpus={}}]", "add `addr` to the list of addresses we listen on" },
350 { "addLuaAction", true, "x, func [, {uuid=\"UUID\"}]", "where 'x' is all the combinations from `addAction`, and func is a function with the parameter `dq`, which returns an action to be taken on this packet. Good for rare packets but where you want to do a lot of processing" },
351 { "addLuaResponseAction", true, "x, func [, {uuid=\"UUID\"}]", "where 'x' is all the combinations from `addAction`, and func is a function with the parameter `dr`, which returns an action to be taken on this response packet. Good for rare packets but where you want to do a lot of processing" },
352 { "addCacheHitResponseAction", true, "DNS rule, DNS response action [, {uuid=\"UUID\"}]", "add a cache hit response rule" },
353 { "addResponseAction", true, "DNS rule, DNS response action [, {uuid=\"UUID\"}]", "add a response rule" },
354 { "addSelfAnsweredResponseAction", true, "DNS rule, DNS response action [, {uuid=\"UUID\"}]", "add a self-answered response rule" },
355 { "addTLSLocal", true, "addr, certFile(s), keyFile(s) [,params]", "listen to incoming DNS over TLS queries on the specified address using the specified certificate (or list of) and key (or list of). The last parameter is a table" },
356 { "AllowAction", true, "", "let these packets go through" },
357 { "AllowResponseAction", true, "", "let these packets go through" },
358 { "AllRule", true, "", "matches all traffic" },
359 { "AndRule", true, "list of DNS rules", "matches if all sub-rules matches" },
360 { "benchRule", true, "DNS Rule [, iterations [, suffix]]", "bench the specified DNS rule" },
361 { "carbonServer", true, "serverIP, [ourname], [interval]", "report statistics to serverIP using our hostname, or 'ourname' if provided, every 'interval' seconds" },
362 { "controlSocket", true, "addr", "open a control socket on this address / connect to this address in client mode" },
363 { "clearDynBlocks", true, "", "clear all dynamic blocks" },
364 { "clearQueryCounters", true, "", "clears the query counter buffer" },
365 { "clearRules", true, "", "remove all current rules" },
366 { "DelayAction", true, "milliseconds", "delay the response by the specified amount of milliseconds (UDP-only)" },
367 { "DelayResponseAction", true, "milliseconds", "delay the response by the specified amount of milliseconds (UDP-only)" },
368 { "delta", true, "", "shows all commands entered that changed the configuration" },
369 { "DisableValidationAction", true, "", "set the CD bit in the question, let it go through" },
370 { "DnstapLogAction", true, "identity, FrameStreamLogger [, alterFunction]", "send the contents of this query to a FrameStreamLogger or RemoteLogger as dnstap. `alterFunction` is a callback, receiving a DNSQuestion and a DnstapMessage, that can be used to modify the dnstap message" },
371 { "DnstapLogResponseAction", true, "identity, FrameStreamLogger [, alterFunction]", "send the contents of this response to a remote or FrameStreamLogger or RemoteLogger as dnstap. `alterFunction` is a callback, receiving a DNSResponse and a DnstapMessage, that can be used to modify the dnstap message" },
372 { "DropAction", true, "", "drop these packets" },
373 { "DropResponseAction", true, "", "drop these packets" },
374 { "DSTPortRule", true, "port", "matches questions received to the destination port specified" },
375 { "dumpStats", true, "", "print all statistics we gather" },
376 { "dynBlockRulesGroup", true, "", "return a new DynBlockRulesGroup object" },
377 { "EDNSVersionRule", true, "version", "matches queries with the specified EDNS version" },
378 { "EDNSOptionRule", true, "optcode", "matches queries with the specified EDNS0 option present" },
379 { "ERCodeAction", true, "ercode", "Reply immediately by turning the query into a response with the specified EDNS extended rcode" },
380 { "ERCodeRule", true, "rcode", "matches responses with the specified extended rcode (EDNS0)" },
381 { "exceedNXDOMAINs", true, "rate, seconds", "get set of addresses that exceed `rate` NXDOMAIN/s over `seconds` seconds" },
382 { "exceedQRate", true, "rate, seconds", "get set of address that exceed `rate` queries/s over `seconds` seconds" },
383 { "exceedQTypeRate", true, "type, rate, seconds", "get set of address that exceed `rate` queries/s for queries of type `type` over `seconds` seconds" },
384 { "exceedRespByterate", true, "rate, seconds", "get set of addresses that exceeded `rate` bytes/s answers over `seconds` seconds" },
385 { "exceedServFails", true, "rate, seconds", "get set of addresses that exceed `rate` servfails/s over `seconds` seconds" },
386 { "firstAvailable", false, "", "picks the server with the lowest `order` that has not exceeded its QPS limit" },
387 { "fixupCase", true, "bool", "if set (default to no), rewrite the first qname of the question part of the answer to match the one from the query. It is only useful when you have a downstream server that messes up the case of the question qname in the answer" },
388 { "generateDNSCryptCertificate", true, "\"/path/to/providerPrivate.key\", \"/path/to/resolver.cert\", \"/path/to/resolver.key\", serial, validFrom, validUntil", "generate a new resolver private key and related certificate, valid from the `validFrom` timestamp until the `validUntil` one, signed with the provider private key" },
389 { "generateDNSCryptProviderKeys", true, "\"/path/to/providerPublic.key\", \"/path/to/providerPrivate.key\"", "generate a new provider keypair" },
390 { "getBind", true, "n", "returns the listener at index n" },
391 { "getDNSCryptBind", true, "n", "return the `DNSCryptContext` object corresponding to the bind `n`" },
392 { "getPool", true, "name", "return the pool named `name`, or \"\" for the default pool" },
393 { "getPoolServers", true, "pool", "return servers part of this pool" },
394 { "getQueryCounters", true, "[max=10]", "show current buffer of query counters, limited by 'max' if provided" },
395 { "getResponseRing", true, "", "return the current content of the response ring" },
396 { "getServer", true, "n", "returns server with index n" },
397 { "getServers", true, "", "returns a table with all defined servers" },
398 { "getTLSContext", true, "n", "returns the TLS context with index n" },
399 { "getTLSFrontend", true, "n", "returns the TLS frontend with index n" },
400 { "inClientStartup", true, "", "returns true during console client parsing of configuration" },
401 { "grepq", true, "Netmask|DNS Name|100ms|{\"::1\", \"powerdns.com\", \"100ms\"} [, n]", "shows the last n queries and responses matching the specified client address or range (Netmask), or the specified DNS Name, or slower than 100ms" },
402 { "leastOutstanding", false, "", "Send traffic to downstream server with least outstanding queries, with the lowest 'order', and within that the lowest recent latency"},
403 { "LogAction", true, "[filename], [binary], [append], [buffered]", "Log a line for each query, to the specified file if any, to the console (require verbose) otherwise. When logging to a file, the `binary` optional parameter specifies whether we log in binary form (default) or in textual form, the `append` optional parameter specifies whether we open the file for appending or truncate each time (default), and the `buffered` optional parameter specifies whether writes to the file are buffered (default) or not." },
404 { "makeKey", true, "", "generate a new server access key, emit configuration line ready for pasting" },
405 { "MaxQPSIPRule", true, "qps, [v4Mask=32 [, v6Mask=64 [, burst=qps [, expiration=300 [, cleanupDelay=60]]]]]", "matches traffic exceeding the qps limit per subnet" },
406 { "MaxQPSRule", true, "qps", "matches traffic **not** exceeding this qps limit" },
407 { "mvCacheHitResponseRule", true, "from, to", "move cache hit response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule" },
408 { "mvResponseRule", true, "from, to", "move response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule" },
409 { "mvRule", true, "from, to", "move rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule, in which case the rule will be moved to the last position" },
410 { "mvSelfAnsweredResponseRule", true, "from, to", "move self-answered response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule" },
411 { "newDNSName", true, "name", "make a DNSName based on this .-terminated name" },
412 { "newFrameStreamTcpLogger", true, "addr", "create a FrameStream logger object writing to a TCP address (addr should be ip:port), to use with `DnstapLogAction()` and `DnstapLogResponseAction()`" },
413 { "newFrameStreamUnixLogger", true, "socket", "create a FrameStream logger object writing to a local unix socket, to use with `DnstapLogAction()` and `DnstapLogResponseAction()`" },
414 { "newPacketCache", true, "maxEntries[, maxTTL=86400, minTTL=0, temporaryFailureTTL=60, staleTTL=60, dontAge=false, numberOfShards=1, deferrableInsertLock=true, options={}]", "return a new Packet Cache" },
415 { "newQPSLimiter", true, "rate, burst", "configure a QPS limiter with that rate and that burst capacity" },
416 { "newRemoteLogger", true, "address:port [, timeout=2, maxQueuedEntries=100, reconnectWaitTime=1]", "create a Remote Logger object, to use with `RemoteLogAction()` and `RemoteLogResponseAction()`" },
417 { "newRuleAction", true, "DNS rule, DNS action [, {uuid=\"UUID\"}]", "return a pair of DNS Rule and DNS Action, to be used with `setRules()`" },
418 { "newServer", true, "{address=\"ip:port\", qps=1000, order=1, weight=10, pool=\"abuse\", retries=5, tcpConnectTimeout=5, tcpSendTimeout=30, tcpRecvTimeout=30, checkName=\"a.root-servers.net.\", checkType=\"A\", maxCheckFailures=1, mustResolve=false, useClientSubnet=true, source=\"address|interface name|address@interface\", sockets=1}", "instantiate a server" },
419 { "newServerPolicy", true, "name, function", "create a policy object from a Lua function" },
420 { "newSuffixMatchNode", true, "", "returns a new SuffixMatchNode" },
421 { "newDNSNameSet", true, "", "returns a new DNSNameSet" },
422 { "NoRecurseAction", true, "", "strip RD bit from the question, let it go through" },
423 { "PoolAction", true, "poolname", "set the packet into the specified pool" },
424 { "printDNSCryptProviderFingerprint", true, "\"/path/to/providerPublic.key\"", "display the fingerprint of the provided resolver public key" },
425 { "QNameLabelsCountRule", true, "min, max", "matches if the qname has less than `min` or more than `max` labels" },
426 { "QNameRule", true, "qname", "matches queries with the specified qname" },
427 { "QNameWireLengthRule", true, "min, max", "matches if the qname's length on the wire is less than `min` or more than `max` bytes" },
428 { "QTypeRule", true, "qtype", "matches queries with the specified qtype" },
429 { "RCodeAction", true, "rcode", "Reply immediately by turning the query into a response with the specified rcode" },
430 { "RCodeRule", true, "rcode", "matches responses with the specified rcode" },
431 { "RegexRule", true, "regex", "matches the query name against the supplied regex" },
432 { "registerDynBPFFilter", true, "DynBPFFilter", "register this dynamic BPF filter into the web interface so that its counters are displayed" },
433 { "RemoteLogAction", true, "RemoteLogger [, alterFunction [, serverID]]", "send the content of this query to a remote logger via Protocol Buffer. `alterFunction` is a callback, receiving a DNSQuestion and a DNSDistProtoBufMessage, that can be used to modify the Protocol Buffer content, for example for anonymization purposes. `serverID` is the server identifier." },
434 { "RemoteLogResponseAction", true, "RemoteLogger [,alterFunction [,includeCNAME [, serverID]]]", "send the content of this response to a remote logger via Protocol Buffer. `alterFunction` is the same callback than the one in `RemoteLogAction` and `includeCNAME` indicates whether CNAME records inside the response should be parsed and exported. The default is to only exports A and AAAA records. `serverID` is the server identifier." },
435 { "rmCacheHitResponseRule", true, "id", "remove cache hit response rule in position 'id', or whose uuid matches if 'id' is an UUID string" },
436 { "rmResponseRule", true, "id", "remove response rule in position 'id', or whose uuid matches if 'id' is an UUID string" },
437 { "rmRule", true, "id", "remove rule in position 'id', or whose uuid matches if 'id' is an UUID string" },
438 { "rmSelfAnsweredResponseRule", true, "id", "remove self-answered response rule in position 'id', or whose uuid matches if 'id' is an UUID string" },
439 { "rmServer", true, "n", "remove server with index n" },
440 { "roundrobin", false, "", "Simple round robin over available servers" },
441 { "sendCustomTrap", true, "str", "send a custom `SNMP` trap from Lua, containing the `str` string"},
442 { "setACL", true, "{netmask, netmask}", "replace the ACL set with these netmasks. Use `setACL({})` to reset the list, meaning no one can use us" },
443 { "setAddEDNSToSelfGeneratedResponses", true, "add", "set whether to add EDNS to self-generated responses, provided that the initial query had EDNS" },
444 { "setAllowEmptyResponse", true, "allow", "Set to true (defaults to false) to allow empty responses (qdcount=0) with a NoError or NXDomain rcode (default) from backends" },
445 { "setAPIWritable", true, "bool, dir", "allow modifications via the API. if `dir` is set, it must be a valid directory where the configuration files will be written by the API" },
446 { "setConsoleACL", true, "{netmask, netmask}", "replace the console ACL set with these netmasks" },
447 { "setConsoleConnectionsLogging", true, "enabled", "whether to log the opening and closing of console connections" },
448 { "setConsoleOutputMaxMsgSize", true, "messageSize", "set console message maximum size in bytes, default is 10 MB" },
449 { "setDNSSECPool", true, "pool name", "move queries requesting DNSSEC processing to this pool" },
450 { "setDynBlocksAction", true, "action", "set which action is performed when a query is blocked. Only DNSAction.Drop (the default) and DNSAction.Refused are supported" },
451 { "setECSOverride", true, "bool", "whether to override an existing EDNS Client Subnet value in the query" },
452 { "setECSSourcePrefixV4", true, "prefix-length", "the EDNS Client Subnet prefix-length used for IPv4 queries" },
453 { "setECSSourcePrefixV6", true, "prefix-length", "the EDNS Client Subnet prefix-length used for IPv6 queries" },
454 { "setKey", true, "key", "set access key to that key" },
455 { "setLocal", true, "addr [, {doTCP=true, reusePort=false, tcpFastOpenSize=0, interface=\"\", cpus={}}]", "reset the list of addresses we listen on to this address" },
456 { "setMaxTCPClientThreads", true, "n", "set the maximum of TCP client threads, handling TCP connections" },
457 { "setMaxTCPConnectionDuration", true, "n", "set the maximum duration of an incoming TCP connection, in seconds. 0 means unlimited" },
458 { "setMaxTCPConnectionsPerClient", true, "n", "set the maximum number of TCP connections per client. 0 means unlimited" },
459 { "setMaxTCPQueriesPerConnection", true, "n", "set the maximum number of queries in an incoming TCP connection. 0 means unlimited" },
460 { "setMaxTCPQueuedConnections", true, "n", "set the maximum number of TCP connections queued (waiting to be picked up by a client thread)" },
461 { "setMaxUDPOutstanding", true, "n", "set the maximum number of outstanding UDP queries to a given backend server. This can only be set at configuration time and defaults to 10240" },
462 { "setPayloadSizeOnSelfGeneratedAnswers", true, "payloadSize", "set the UDP payload size advertised via EDNS on self-generated responses" },
463 { "setPoolServerPolicy", true, "policy, pool", "set the server selection policy for this pool to that policy" },
464 { "setPoolServerPolicy", true, "name, func, pool", "set the server selection policy for this pool to one named 'name' and provided by 'function'" },
465 { "setPreserveTrailingData", true, "bool", "set whether trailing data should be preserved while adding ECS or XPF records to incoming queries" },
466 { "setQueryCount", true, "bool", "set whether queries should be counted" },
467 { "setQueryCountFilter", true, "func", "filter queries that would be counted, where `func` is a function with parameter `dq` which decides whether a query should and how it should be counted" },
468 { "setRingBuffersLockRetries", true, "n", "set the number of attempts to get a non-blocking lock to a ringbuffer shard before blocking" },
469 { "setRingBuffersSize", true, "n [, numberOfShards]", "set the capacity of the ringbuffers used for live traffic inspection to `n`, and optionally the number of shards to use to `numberOfShards`" },
470 { "setRules", true, "list of rules", "replace the current rules with the supplied list of pairs of DNS Rules and DNS Actions (see `newRuleAction()`)" },
471 { "setSecurityPollInterval", true, "n", "set the security polling interval to `n` seconds" },
472 { "setSecurityPollSuffix", true, "suffix", "set the security polling suffix to the specified value" },
473 { "setServerPolicy", true, "policy", "set server selection policy to that policy" },
474 { "setServerPolicyLua", true, "name, function", "set server selection policy to one named 'name' and provided by 'function'" },
475 { "setServFailWhenNoServer", true, "bool", "if set, return a ServFail when no servers are available, instead of the default behaviour of dropping the query" },
476 { "setStaleCacheEntriesTTL", true, "n", "allows using cache entries expired for at most n seconds when there is no backend available to answer for a query" },
477 { "setTCPDownstreamCleanupInterval", true, "interval", "minimum interval in seconds between two cleanups of the idle TCP downstream connections" },
478 { "setTCPUseSinglePipe", true, "bool", "whether the incoming TCP connections should be put into a single queue instead of using per-thread queues. Defaults to false" },
479 { "setTCPRecvTimeout", true, "n", "set the read timeout on TCP connections from the client, in seconds" },
480 { "setTCPSendTimeout", true, "n", "set the write timeout on TCP connections from the client, in seconds" },
481 { "setUDPMultipleMessagesVectorSize", true, "n", "set the size of the vector passed to recvmmsg() to receive UDP messages. Default to 1 which means that the feature is disabled and recvmsg() is used instead" },
482 { "setUDPTimeout", true, "n", "set the maximum time dnsdist will wait for a response from a backend over UDP, in seconds" },
483 { "setVerboseHealthChecks", true, "bool", "set whether health check errors will be logged" },
484 { "setWebserverConfig", true, "[{password=string, apiKey=string, customHeaders}]", "Updates webserver configuration" },
485 { "show", true, "string", "outputs `string`" },
486 { "showACL", true, "", "show our ACL set" },
487 { "showBinds", true, "", "show listening addresses (frontends)" },
488 { "showCacheHitResponseRules", true, "[{showUUIDs=false, truncateRuleWidth=-1}]", "show all defined cache hit response rules, optionally with their UUIDs and optionally truncated to a given width" },
489 { "showConsoleACL", true, "", "show our current console ACL set" },
490 { "showDNSCryptBinds", true, "", "display the currently configured DNSCrypt binds" },
491 { "showDynBlocks", true, "", "show dynamic blocks in force" },
492 { "showPools", true, "", "show the available pools" },
493 { "showPoolServerPolicy", true, "pool", "show server selection policy for this pool" },
494 { "showResponseLatency", true, "", "show a plot of the response time latency distribution" },
495 { "showResponseRules", true, "[{showUUIDs=false, truncateRuleWidth=-1}]", "show all defined response rules, optionally with their UUIDs and optionally truncated to a given width" },
496 { "showRules", true, "[{showUUIDs=false, truncateRuleWidth=-1}]", "show all defined rules, optionally with their UUIDs and optionally truncated to a given width" },
497 { "showSecurityStatus", true, "", "Show the security status"},
498 { "showSelfAnsweredResponseRules", true, "[{showUUIDs=false, truncateRuleWidth=-1}]", "show all defined self-answered response rules, optionally with their UUIDs and optionally truncated to a given width" },
499 { "showServerPolicy", true, "", "show name of currently operational server selection policy" },
500 { "showServers", true, "[{showUUIDs=false}]", "output all servers, optionally with their UUIDs" },
501 { "showTCPStats", true, "", "show some statistics regarding TCP" },
502 { "showTLSContexts", true, "", "list all the available TLS contexts" },
503 { "showVersion", true, "", "show the current version" },
504 { "shutdown", true, "", "shut down `dnsdist`" },
505 { "snmpAgent", true, "enableTraps [, masterSocket]", "enable `SNMP` support. `enableTraps` is a boolean indicating whether traps should be sent and `masterSocket` an optional string specifying how to connect to the master agent"},
506 { "SNMPTrapAction", true, "[reason]", "send an SNMP trap, adding the optional `reason` string as the query description"},
507 { "SNMPTrapResponseAction", true, "[reason]", "send an SNMP trap, adding the optional `reason` string as the response description"},
508 { "SpoofAction", true, "{ip, ...} ", "forge a response with the specified IPv4 (for an A query) or IPv6 (for an AAAA). If you specify multiple addresses, all that match the query type (A, AAAA or ANY) will get spoofed in" },
509 { "TagAction", true, "name, value", "set the tag named 'name' to the given value" },
510 { "TagResponseAction", true, "name, value", "set the tag named 'name' to the given value" },
511 { "TagRule", true, "name [, value]", "matches if the tag named 'name' is present, with the given 'value' matching if any" },
512 { "TCAction", true, "", "create answer to query with TC and RD bits set, to move to TCP" },
513 { "TeeAction", true, "remote [, addECS]", "send copy of query to remote, optionally adding ECS info" },
514 { "TempFailureCacheTTLAction", true, "ttl", "set packetcache TTL for temporary failure replies" },
515 { "testCrypto", true, "", "test of the crypto all works" },
516 { "TimedIPSetRule", true, "", "Create a rule which matches a set of IP addresses which expire"},
517 { "topBandwidth", true, "top", "show top-`top` clients that consume the most bandwidth over length of ringbuffer" },
518 { "topCacheHitResponseRule", true, "", "move the last cache hit response rule to the first position" },
519 { "topClients", true, "n", "show top-`n` clients sending the most queries over length of ringbuffer" },
520 { "topQueries", true, "n[, labels]", "show top 'n' queries, as grouped when optionally cut down to 'labels' labels" },
521 { "topResponses", true, "n, kind[, labels]", "show top 'n' responses with RCODE=kind (0=NO Error, 2=ServFail, 3=ServFail), as grouped when optionally cut down to 'labels' labels" },
522 { "topCacheHitResponseRule", true, "", "move the last cache hit response rule to the first position" },
523 { "topResponseRule", true, "", "move the last response rule to the first position" },
524 { "topRule", true, "", "move the last rule to the first position" },
525 { "topSelfAnsweredResponseRule", true, "", "move the last self-answered response rule to the first position" },
526 { "topSlow", true, "[top][, limit][, labels]", "show `top` queries slower than `limit` milliseconds, grouped by last `labels` labels" },
527 { "truncateTC", true, "bool", "if set (defaults to no starting with dnsdist 1.2.0) truncate TC=1 answers so they are actually empty. Fixes an issue for PowerDNS Authoritative Server 2.9.22. Note: turning this on breaks compatibility with RFC 6891." },
528 { "unregisterDynBPFFilter", true, "DynBPFFilter", "unregister this dynamic BPF filter" },
529 { "webserver", true, "address:port, password [, apiKey [, customHeaders ]])", "launch a webserver with stats on that address with that password" },
530 { "whashed", false, "", "Weighted hashed ('sticky') distribution over available servers, based on the server 'weight' parameter" },
531 { "chashed", false, "", "Consistent hashed ('sticky') distribution over available servers, also based on the server 'weight' parameter" },
532 { "wrandom", false, "", "Weighted random over available servers, based on the server 'weight' parameter" },
536 char* my_generator(const char* text
, int state
)
539 /* to keep it readable, we try to keep only 4 keywords per line
540 and to start a new line when the first letter changes */
541 static int s_counter
=0;
546 for(const auto& keyword
: g_consoleKeywords
) {
547 if(boost::starts_with(keyword
.name
, t
) && counter
++ == s_counter
) {
548 std::string
value(keyword
.name
);
550 if (keyword
.function
) {
552 if (keyword
.parameters
.empty()) {
556 return strdup(value
.c_str());
562 char** my_completion( const char * text
, int start
, int end
)
566 matches
= rl_completion_matches ((char*)text
, &my_generator
);
568 // skip default filename completion.
569 rl_attempted_completion_over
= 1;
575 static void controlClientThread(int fd
, ComboAddress client
)
578 setThreadName("dnsdist/conscli");
580 SodiumNonce theirs
, ours
, readingNonce
, writingNonce
;
582 readn2(fd
, (char*)theirs
.value
, sizeof(theirs
.value
));
583 writen2(fd
, (char*)ours
.value
, sizeof(ours
.value
));
584 readingNonce
.merge(ours
, theirs
);
585 writingNonce
.merge(theirs
, ours
);
589 if(!getMsgLen32(fd
, &len
))
593 /* just ACK an empty message
594 with an empty response */
599 boost::scoped_array
<char> msg(new char[len
]);
600 readn2(fd
, msg
.get(), len
);
602 string
line(msg
.get(), len
);
604 line
= sodDecryptSym(line
, g_consoleKey
, readingNonce
);
605 // cerr<<"Have decrypted line: "<<line<<endl;
608 bool withReturn
=true;
611 std::lock_guard
<std::mutex
> lock(g_luamutex
);
613 g_outputBuffer
.clear();
614 resetLuaSideEffect();
615 auto ret
=g_lua
.executeCode
<
619 shared_ptr
<DownstreamState
>,
621 std::unordered_map
<string
, double>
624 >(withReturn
? ("return "+line
) : line
);
627 if (const auto dsValue
= boost::get
<shared_ptr
<DownstreamState
>>(&*ret
)) {
629 response
=(*dsValue
)->getName()+"\n";
634 else if (const auto csValue
= boost::get
<ClientState
*>(&*ret
)) {
636 response
=(*csValue
)->local
.toStringWithPort()+"\n";
641 else if (const auto strValue
= boost::get
<string
>(&*ret
)) {
642 response
=*strValue
+"\n";
644 else if(const auto um
= boost::get
<std::unordered_map
<string
, double> >(&*ret
)) {
645 using namespace json11
;
647 for(const auto& v
: *um
)
650 response
=out
.dump()+"\n";
654 response
=g_outputBuffer
;
655 if(!getLuaNoSideEffect())
656 feedConfigDelta(line
);
658 catch(const LuaContext::SyntaxErrorException
&) {
666 catch(const LuaContext::WrongTypeException
& e
) {
667 response
= "Command returned an object we can't print: " +std::string(e
.what()) + "\n";
668 // tried to return something we don't understand
670 catch(const LuaContext::ExecutionErrorException
& e
) {
671 if(!strcmp(e
.what(),"invalid key to 'next'"))
672 response
= "Error: Parsing function parameters, did you forget parameter name?";
674 response
= "Error: " + string(e
.what());
676 std::rethrow_if_nested(e
);
677 } catch(const std::exception
& ne
) {
678 // ne is the exception that was thrown from inside the lambda
679 response
+= ": " + string(ne
.what());
681 catch(const PDNSException
& ne
) {
682 // ne is the exception that was thrown from inside the lambda
683 response
+= ": " + string(ne
.reason
);
686 catch(const LuaContext::SyntaxErrorException
& e
) {
687 response
= "Error: " + string(e
.what()) + ": ";
689 response
= sodEncryptSym(response
, g_consoleKey
, writingNonce
);
690 putMsgLen32(fd
, response
.length());
691 writen2(fd
, response
.c_str(), response
.length());
693 if (g_logConsoleConnections
) {
694 infolog("Closed control connection from %s", client
.toStringWithPort());
699 catch(std::exception
& e
)
701 errlog("Got an exception in client connection from %s: %s", client
.toStringWithPort(), e
.what());
706 void controlThread(int fd
, ComboAddress local
)
709 setThreadName("dnsdist/control");
712 auto localACL
= g_consoleACL
.getLocal();
713 infolog("Accepting control connections on %s", local
.toStringWithPort());
715 while ((sock
= SAccept(fd
, client
)) >= 0) {
717 if (!sodIsValidKey(g_consoleKey
)) {
718 vinfolog("Control connection from %s dropped because we don't have a valid key configured, please configure one using setKey()", client
.toStringWithPort());
723 if (!localACL
->match(client
)) {
724 vinfolog("Control connection from %s dropped because of ACL", client
.toStringWithPort());
729 if (g_logConsoleConnections
) {
730 warnlog("Got control connection from %s", client
.toStringWithPort());
733 std::thread
t(controlClientThread
, sock
, client
);
737 catch(const std::exception
& e
)
740 errlog("Control connection died: %s", e
.what());