]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/dnsdist-console.cc
Merge pull request #5543 from rgacogne/web-auto-ptr
[thirdparty/pdns.git] / pdns / dnsdist-console.cc
CommitLineData
12471842
PL
1/*
2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
4 *
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.
8 *
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.
12 *
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.
17 *
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.
21 */
ffb07158 22#include "dnsdist.hh"
23#include "sodcrypto.hh"
93644039 24#include "pwd.h"
424bdfb1
PD
25
26#if defined (__OpenBSD__)
27#include <readline/readline.h>
28#include <readline/history.h>
29#else
9c8aa826 30#include <editline/readline.h>
424bdfb1
PD
31#endif
32
ffb07158 33#include <fstream>
34#include "dolog.hh"
7e7a5b71 35#include "ext/json11/json11.hpp"
ffb07158 36
f758857a 37vector<pair<struct timeval, string> > g_confDelta;
38
39// MUST BE CALLED UNDER A LOCK - right now the LuaLock
40void feedConfigDelta(const std::string& line)
41{
171fca9a 42 if(line.empty())
43 return;
f758857a 44 struct timeval now;
45 gettimeofday(&now, 0);
46 g_confDelta.push_back({now,line});
47}
48
93644039
PL
49string historyFile(const bool &ignoreHOME = false)
50{
51 string ret;
52
53 struct passwd pwd;
54 struct passwd *result;
55 char buf[16384];
56 getpwuid_r(geteuid(), &pwd, buf, sizeof(buf), &result);
57
58 const char *homedir = getenv("HOME");
59 if (result)
60 ret = string(pwd.pw_dir);
61 if (homedir && !ignoreHOME) // $HOME overrides what the OS tells us
62 ret = string(homedir);
63 if (ret.empty())
64 ret = "."; // CWD if nothing works..
65 ret.append("/.dnsdist_history");
66 return ret;
67}
68
ffb07158 69void doClient(ComboAddress server, const std::string& command)
70{
9c186303 71 if(g_verbose)
72 cout<<"Connecting to "<<server.toStringWithPort()<<endl;
ffb07158 73 int fd=socket(server.sin4.sin_family, SOCK_STREAM, 0);
6a62c0e3
RG
74 if (fd < 0) {
75 cerr<<"Unable to connect to "<<server.toStringWithPort()<<endl;
76 return;
77 }
ffb07158 78 SConnect(fd, server);
9c186303 79 setTCPNoDelay(fd);
333ea16e 80 SodiumNonce theirs, ours, readingNonce, writingNonce;
ffb07158 81 ours.init();
82
83 writen2(fd, (const char*)ours.value, sizeof(ours.value));
84 readn2(fd, (char*)theirs.value, sizeof(theirs.value));
333ea16e
RG
85 readingNonce.merge(ours, theirs);
86 writingNonce.merge(theirs, ours);
ffb07158 87
88 if(!command.empty()) {
333ea16e 89 string msg=sodEncryptSym(command, g_key, writingNonce);
a683e8bd 90 putMsgLen32(fd, (uint32_t) msg.length());
ffb07158 91 if(!msg.empty())
92 writen2(fd, msg);
93 uint32_t len;
e4ef64be
RG
94 if(getMsgLen32(fd, &len)) {
95 if (len > 0) {
96 boost::scoped_array<char> resp(new char[len]);
97 readn2(fd, resp.get(), len);
98 msg.assign(resp.get(), len);
333ea16e 99 msg=sodDecryptSym(msg, g_key, readingNonce);
7e7a5b71 100 cout<<msg;
a43f9501 101 cout.flush();
e4ef64be
RG
102 }
103 }
104 else {
105 cout << "Connection closed by the server." << endl;
106 }
6a62c0e3 107 close(fd);
ffb07158 108 return;
109 }
110
93644039 111 string histfile = historyFile();
ffb07158 112 set<string> dupper;
113 {
93644039 114 ifstream history(histfile);
ffb07158 115 string line;
116 while(getline(history, line))
117 add_history(line.c_str());
118 }
93644039 119 ofstream history(histfile, std::ios_base::app);
ffb07158 120 string lastline;
121 for(;;) {
122 char* sline = readline("> ");
123 rl_bind_key('\t',rl_complete);
124 if(!sline)
125 break;
126
127 string line(sline);
128 if(!line.empty() && line != lastline) {
129 add_history(sline);
130 history << sline <<endl;
131 history.flush();
132 }
133 lastline=line;
134 free(sline);
135
136 if(line=="quit")
137 break;
138
0877da98
RG
139 /* no need to send an empty line to the server */
140 if(line.empty())
141 continue;
142
333ea16e 143 string msg=sodEncryptSym(line, g_key, writingNonce);
a683e8bd 144 putMsgLen32(fd, (uint32_t) msg.length());
ffb07158 145 writen2(fd, msg);
146 uint32_t len;
ff16861c 147 if(!getMsgLen32(fd, &len)) {
ffb07158 148 cout << "Connection closed by the server." << endl;
149 break;
150 }
151
ff16861c
RG
152 if (len > 0) {
153 boost::scoped_array<char> resp(new char[len]);
154 readn2(fd, resp.get(), len);
155 msg.assign(resp.get(), len);
333ea16e 156 msg=sodDecryptSym(msg, g_key, readingNonce);
7e7a5b71 157 cout<<msg;
158 cout.flush();
ff16861c
RG
159 }
160 else {
161 cout<<endl;
162 }
ffb07158 163 }
6c1ca990 164 close(fd);
ffb07158 165}
166
167void doConsole()
168{
93644039 169 string histfile = historyFile(true);
ffb07158 170 set<string> dupper;
171 {
93644039 172 ifstream history(histfile);
ffb07158 173 string line;
174 while(getline(history, line))
175 add_history(line.c_str());
176 }
93644039 177 ofstream history(histfile, std::ios_base::app);
ffb07158 178 string lastline;
179 for(;;) {
180 char* sline = readline("> ");
181 rl_bind_key('\t',rl_complete);
182 if(!sline)
183 break;
184
185 string line(sline);
186 if(!line.empty() && line != lastline) {
187 add_history(sline);
188 history << sline <<endl;
189 history.flush();
190 }
191 lastline=line;
192 free(sline);
193
194 if(line=="quit")
195 break;
196
197 string response;
198 try {
7e7a5b71 199 bool withReturn=true;
200 retry:;
201 try {
202 std::lock_guard<std::mutex> lock(g_luamutex);
203 g_outputBuffer.clear();
204 resetLuaSideEffect();
205 auto ret=g_lua.executeCode<
206 boost::optional<
207 boost::variant<
208 string,
209 shared_ptr<DownstreamState>,
210 std::unordered_map<string, double>
211 >
212 >
213 >(withReturn ? ("return "+line) : line);
7e7a5b71 214 if(ret) {
af619119
RG
215 if (const auto dsValue = boost::get<shared_ptr<DownstreamState>>(&*ret)) {
216 cout<<(*dsValue)->getName()<<endl;
7e7a5b71 217 }
218 else if (const auto strValue = boost::get<string>(&*ret)) {
219 cout<<*strValue<<endl;
220 }
221 else if(const auto um = boost::get<std::unordered_map<string, double> >(&*ret)) {
222 using namespace json11;
223 Json::object o;
224 for(const auto& v : *um)
225 o[v.first]=v.second;
226 Json out = o;
227 cout<<out.dump()<<endl;
228 }
229 }
230 else
231 cout << g_outputBuffer;
232 if(!getLuaNoSideEffect())
233 feedConfigDelta(line);
ffb07158 234 }
7e7a5b71 235 catch(const LuaContext::SyntaxErrorException&) {
236 if(withReturn) {
237 withReturn=false;
238 goto retry;
239 }
240 throw;
241 }
242 }
243 catch(const LuaContext::WrongTypeException& e) {
244 std::cerr<<"Command returned an object we can't print"<<std::endl;
245 // tried to return something we don't understand
ffb07158 246 }
247 catch(const LuaContext::ExecutionErrorException& e) {
98bac6bc 248 if(!strcmp(e.what(),"invalid key to 'next'"))
249 std::cerr<<"Error parsing parameters, did you forget parameter name?";
250 else
251 std::cerr << e.what();
ffb07158 252 try {
253 std::rethrow_if_nested(e);
98bac6bc 254
d96df9ec 255 std::cerr << std::endl;
af619119
RG
256 } catch(const std::exception& ne) {
257 // ne is the exception that was thrown from inside the lambda
258 std::cerr << ": " << ne.what() << std::endl;
ffb07158 259 }
af619119
RG
260 catch(const PDNSException& ne) {
261 // ne is the exception that was thrown from inside the lambda
262 std::cerr << ": " << ne.reason << std::endl;
ffb07158 263 }
264 }
265 catch(const std::exception& e) {
ffb07158 266 std::cerr << e.what() << std::endl;
267 }
268 }
269}
270/**** CARGO CULT CODE AHEAD ****/
ca4252e0
RG
271const std::vector<ConsoleKeyword> g_consoleKeywords{
272 /* keyword, function, parameters, description */
273 { "addACL", true, "netmask", "add to the ACL set who can use this server" },
274 { "addAction", true, "DNS rule, DNS action", "add a rule" },
275 { "addAnyTCRule", true, "", "generate TC=1 answers to ANY queries received over UDP, moving them to TCP" },
276 { "addDelay", true, "domain, n", "delay answers within that domain by n milliseconds" },
277 { "addDisableValidationRule", true, "DNS rule", "set the CD flags to 1 for all queries matching the specified domain" },
efd35aa8 278 { "addDNSCryptBind", true, "\"127.0.0.1:8443\", \"provider name\", \"/path/to/resolver.cert\", \"/path/to/resolver.key\", {reusePort=false, tcpFastOpenSize=0, interface=\"\"}", "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" },
ca4252e0
RG
279 { "addDomainBlock", true, "domain", "block queries within this domain" },
280 { "addDomainSpoof", true, "domain, ip[, ip6]", "generate answers for A/AAAA/ANY queries using the ip parameters" },
7b925432 281 { "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()`)" },
efd35aa8 282 { "addLocal", true, "addr [, {doTCP=true, reusePort=false, tcpFastOpenSize=0, interface=\"\"}]", "add `addr` to the list of addresses we listen on" },
ca4252e0 283 { "addLuaAction", true, "x, func", "where 'x' is all the combinations from `addPoolRule`, 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" },
153d5065 284 { "addLuaResponseAction", true, "x, func", "where 'x' is all the combinations from `addPoolRule`, 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" },
ca4252e0
RG
285 { "addNoRecurseRule", true, "domain", "clear the RD flag for all queries matching the specified domain" },
286 { "addPoolRule", true, "domain, pool", "send queries to this domain to that pool" },
287 { "addQPSLimit", true, "domain, n", "limit queries within that domain to n per second" },
288 { "addQPSPoolRule", true, "x, limit, pool", "like `addPoolRule`, but only select at most 'limit' queries/s for this pool, letting the subsequent rules apply otherwise" },
cf48b0ce 289 { "addCacheHitResponseAction", true, "DNS rule, DNS response action", "add a cache hit response rule" },
ca4252e0
RG
290 { "addResponseAction", true, "DNS rule, DNS response action", "add a response rule" },
291 { "AllowAction", true, "", "let these packets go through" },
788c3243 292 { "AllowResponseAction", true, "", "let these packets go through" },
ca4252e0
RG
293 { "AllRule", true, "", "matches all traffic" },
294 { "AndRule", true, "list of DNS rules", "matches if all sub-rules matches" },
295 { "benchRule", true, "DNS Rule [, iterations [, suffix]]", "bench the specified DNS rule" },
296 { "carbonServer", true, "serverIP, [ourname], [interval]", "report statistics to serverIP using our hostname, or 'ourname' if provided, every 'interval' seconds" },
297 { "controlSocket", true, "addr", "open a control socket on this address / connect to this address in client mode" },
298 { "clearDynBlocks", true, "", "clear all dynamic blocks" },
786e4d8c 299 { "clearQueryCounters", true, "", "clears the query counter buffer" },
ca4252e0
RG
300 { "clearRules", true, "", "remove all current rules" },
301 { "DelayAction", true, "milliseconds", "delay the response by the specified amount of milliseconds (UDP-only)" },
788c3243 302 { "DelayResponseAction", true, "milliseconds", "delay the response by the specified amount of milliseconds (UDP-only)" },
ca4252e0
RG
303 { "delta", true, "", "shows all commands entered that changed the configuration" },
304 { "DisableValidationAction", true, "", "set the CD bit in the question, let it go through" },
305 { "DropAction", true, "", "drop these packets" },
788c3243 306 { "DropResponseAction", true, "", "drop these packets" },
ca4252e0
RG
307 { "dumpStats", true, "", "print all statistics we gather" },
308 { "exceedNXDOMAINs", true, "rate, seconds", "get set of addresses that exceed `rate` NXDOMAIN/s over `seconds` seconds" },
309 { "exceedQRate", true, "rate, seconds", "get set of address that exceed `rate` queries/s over `seconds` seconds" },
310 { "exceedQTypeRate", true, "type, rate, seconds", "get set of address that exceed `rate` queries/s for queries of type `type` over `seconds` seconds" },
55fcc3d7 311 { "exceedRespByterate", true, "rate, seconds", "get set of addresses that exceeded `rate` bytes/s answers over `seconds` seconds" },
82e55fbd 312 { "exceedServFails", true, "rate, seconds", "get set of addresses that exceed `rate` servfails/s over `seconds` seconds" },
ca4252e0
RG
313 { "firstAvailable", false, "", "picks the server with the lowest `order` that has not exceeded its QPS limit" },
314 { "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" },
315 { "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" },
79500db5
RG
316 { "generateDNSCryptProviderKeys", true, "\"/path/to/providerPublic.key\", \"/path/to/providerPrivate.key\"", "generate a new provider keypair" },
317 { "getDNSCryptBind", true, "n", "return the `DNSCryptContext` object corresponding to the bind `n`" },
ca4252e0 318 { "getPoolServers", true, "pool", "return servers part of this pool" },
786e4d8c 319 { "getQueryCounters", true, "[max=10]", "show current buffer of query counters, limited by 'max' if provided" },
ca4252e0
RG
320 { "getResponseRing", true, "", "return the current content of the response ring" },
321 { "getServer", true, "n", "returns server with index n" },
322 { "getServers", true, "", "returns a table with all defined servers" },
323 { "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" },
324 { "leastOutstanding", false, "", "Send traffic to downstream server with least outstanding queries, with the lowest 'order', and within that the lowest recent latency"},
456fc645 325 { "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." },
ca4252e0
RG
326 { "makeKey", true, "", "generate a new server access key, emit configuration line ready for pasting" },
327 { "MaxQPSIPRule", true, "qps, v4Mask=32, v6Mask=64", "matches traffic exceeding the qps limit per subnet" },
328 { "MaxQPSRule", true, "qps", "matches traffic **not** exceeding this qps limit" },
cf48b0ce 329 { "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" },
ca4252e0
RG
330 { "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" },
331 { "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" },
332 { "newDNSName", true, "name", "make a DNSName based on this .-terminated name" },
2b67180c 333 { "newPacketCache", true, "maxEntries[, maxTTL=86400, minTTL=0, temporaryFailureTTL=60, staleTTL=60, dontAge=false]", "return a new Packet Cache" },
ca4252e0
RG
334 { "newQPSLimiter", true, "rate, burst", "configure a QPS limiter with that rate and that burst capacity" },
335 { "newRemoteLogger", true, "address:port [, timeout=2, maxQueuedEntries=100, reconnectWaitTime=1]", "create a Remote Logger object, to use with `RemoteLogAction()` and `RemoteLogResponseAction()`" },
336 { "newRuleAction", true, "DNS rule, DNS action", "return a pair of DNS Rule and DNS Action, to be used with `setRules()`" },
efd35aa8 337 { "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\"}", "instantiate a server" },
ca4252e0
RG
338 { "newServerPolicy", true, "name, function", "create a policy object from a Lua function" },
339 { "newSuffixMatchNode", true, "", "returns a new SuffixMatchNode" },
340 { "NoRecurseAction", true, "", "strip RD bit from the question, let it go through" },
341 { "PoolAction", true, "poolname", "set the packet into the specified pool" },
342 { "printDNSCryptProviderFingerprint", true, "\"/path/to/providerPublic.key\"", "display the fingerprint of the provided resolver public key" },
343 { "RegexRule", true, "regex", "matches the query name against the supplied regex" },
8429ad04 344 { "registerDynBPFFilter", true, "DynBPFFilter", "register this dynamic BPF filter into the web interface so that its counters are displayed" },
165c9030
RG
345 { "RemoteLogAction", true, "RemoteLogger [, alterFunction]", "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" },
346 { "RemoteLogResponseAction", true, "RemoteLogger [,alterFunction [,includeCNAME]]", "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" },
cf48b0ce 347 { "rmCacheHitResponseRule", true, "n", "remove cache hit response rule n" },
ca4252e0
RG
348 { "rmResponseRule", true, "n", "remove response rule n" },
349 { "rmRule", true, "n", "remove rule n" },
350 { "rmServer", true, "n", "remove server with index n" },
351 { "roundrobin", false, "", "Simple round robin over available servers" },
352 { "QNameLabelsCountRule", true, "min, max", "matches if the qname has less than `min` or more than `max` labels" },
0f697c45 353 { "QNameRule", true, "qname", "matches queries with the specified qname" },
ca4252e0
RG
354 { "QNameWireLengthRule", true, "min, max", "matches if the qname's length on the wire is less than `min` or more than `max` bytes" },
355 { "QTypeRule", true, "qtype", "matches queries with the specified qtype" },
788c3243 356 { "RCodeRule", true, "rcode", "matches responses with the specified rcode" },
9f4eb5cc 357 { "sendCustomTrap", true, "str", "send a custom `SNMP` trap from Lua, containing the `str` string"},
ca4252e0 358 { "setACL", true, "{netmask, netmask}", "replace the ACL set with these netmasks. Use `setACL({})` to reset the list, meaning no one can use us" },
56d68fad 359 { "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" },
ca4252e0 360 { "setDNSSECPool", true, "pool name", "move queries requesting DNSSEC processing to this pool" },
7b925432 361 { "setDynBlocksAction", true, "action", "set which action is performed when a query is blocked. Only DNSAction.Drop (the default) and DNSAction.Refused are supported" },
ca4252e0
RG
362 { "setECSOverride", true, "bool", "whether to override an existing EDNS Client Subnet value in the query" },
363 { "setECSSourcePrefixV4", true, "prefix-length", "the EDNS Client Subnet prefix-length used for IPv4 queries" },
364 { "setECSSourcePrefixV6", true, "prefix-length", "the EDNS Client Subnet prefix-length used for IPv6 queries" },
365 { "setKey", true, "key", "set access key to that key" },
efd35aa8 366 { "setLocal", true, "addr [, {doTCP=true, reusePort=false, tcpFastOpenSize=0, interface=\"\"}]", "reset the list of addresses we listen on to this address" },
ca4252e0 367 { "setMaxTCPClientThreads", true, "n", "set the maximum of TCP client threads, handling TCP connections" },
9396d955
RG
368 { "setMaxTCPConnectionDuration", true, "n", "set the maximum duration of an incoming TCP connection, in seconds. 0 means unlimited" },
369 { "setMaxTCPConnectionsPerClient", true, "n", "set the maximum number of TCP connections per client. 0 means unlimited" },
370 { "setMaxTCPQueriesPerConnection", true, "n", "set the maximum number of queries in an incoming TCP connection. 0 means unlimited" },
ca4252e0
RG
371 { "setMaxTCPQueuedConnections", true, "n", "set the maximum number of TCP connections queued (waiting to be picked up by a client thread)" },
372 { "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" },
742c079a
RG
373 { "setPoolServerPolicy", true, "policy, pool", "set the server selection policy for this pool to that policy" },
374 { "setPoolServerPolicy", true, "name, func, pool", "set the server selection policy for this pool to one named 'name' and provided by 'function'" },
786e4d8c
RS
375 { "setQueryCount", true, "bool", "set whether queries should be counted" },
376 { "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" },
e4c24bb3 377 { "setRingBuffersSize", true, "n", "set the capacity of the ringbuffers used for live traffic inspection to `n`" },
ca4252e0
RG
378 { "setRules", true, "list of rules", "replace the current rules with the supplied list of pairs of DNS Rules and DNS Actions (see `newRuleAction()`)" },
379 { "setServerPolicy", true, "policy", "set server selection policy to that policy" },
380 { "setServerPolicyLua", true, "name, function", "set server selection policy to one named 'name' and provided by 'function'" },
26a3cdb7 381 { "setServFailWhenNoServer", true, "bool", "if set, return a ServFail when no servers are available, instead of the default behaviour of dropping the query" },
840ed663 382 { "setTCPDownstreamCleanupInterval", true, "interval", "minimum interval in seconds between two cleanups of the idle TCP downstream connections" },
edbda1ad 383 { "setTCPUseSinglePipe", true, "bool", "whether the incoming TCP connections should be put into a single queue instead of using per-thread queues. Defaults to false" },
ca4252e0
RG
384 { "setTCPRecvTimeout", true, "n", "set the read timeout on TCP connections from the client, in seconds" },
385 { "setTCPSendTimeout", true, "n", "set the write timeout on TCP connections from the client, in seconds" },
e0b5e49d 386 { "setUDPTimeout", true, "n", "set the maximum time dnsdist will wait for a response from a backend over UDP, in seconds" },
ca4252e0
RG
387 { "setVerboseHealthChecks", true, "bool", "set whether health check errors will be logged" },
388 { "show", true, "string", "outputs `string`" },
389 { "showACL", true, "", "show our ACL set" },
cf48b0ce 390 { "showCacheHitResponseRules", true, "", "show all defined cache hit response rules" },
ca4252e0
RG
391 { "showDNSCryptBinds", true, "", "display the currently configured DNSCrypt binds" },
392 { "showDynBlocks", true, "", "show dynamic blocks in force" },
742c079a 393 { "showPoolServerPolicy", true, "pool", "show server selection policy for this pool" },
ca4252e0
RG
394 { "showResponseLatency", true, "", "show a plot of the response time latency distribution" },
395 { "showResponseRules", true, "", "show all defined response rules" },
396 { "showRules", true, "", "show all defined rules" },
397 { "showServerPolicy", true, "", "show name of currently operational server selection policy" },
398 { "showServers", true, "", "output all servers" },
e65ae260 399 { "showTCPStats", true, "", "show some statistics regarding TCP" },
ca4252e0
RG
400 { "showVersion", true, "", "show the current version" },
401 { "shutdown", true, "", "shut down `dnsdist`" },
9f4eb5cc
RG
402 { "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"},
403 { "SNMPTrapAction", true, "[reason]", "send an SNMP trap, adding the optional `reason` string as the query description"},
404 { "SNMPTrapResponseAction", true, "[reason]", "send an SNMP trap, adding the optional `reason` string as the response description"},
ca4252e0
RG
405 { "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" },
406 { "TCAction", true, "", "create answer to query with TC and RD bits set, to move to TCP" },
407 { "testCrypto", true, "", "test of the crypto all works" },
01b8149e 408 { "TimedIPSetRule", true, "", "Create a rule which matches a set of IP addresses which expire"},
ca4252e0 409 { "topBandwidth", true, "top", "show top-`top` clients that consume the most bandwidth over length of ringbuffer" },
cf48b0ce 410 { "topCacheHitResponseRule", true, "", "move the last cache hit response rule to the first position" },
ca4252e0
RG
411 { "topClients", true, "n", "show top-`n` clients sending the most queries over length of ringbuffer" },
412 { "topQueries", true, "n[, labels]", "show top 'n' queries, as grouped when optionally cut down to 'labels' labels" },
413 { "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" },
414 { "topResponseRule", true, "", "move the last response rule to the first position" },
415 { "topRule", true, "", "move the last rule to the first position" },
416 { "topSlow", true, "[top][, limit][, labels]", "show `top` queries slower than `limit` milliseconds, grouped by last `labels` labels" },
e72fbfc4 417 { "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." },
8429ad04 418 { "unregisterDynBPFFilter", true, "DynBPFFilter", "unregister this dynamic BPF filter" },
ca4252e0
RG
419 { "webserver", true, "address:port, password [, apiKey [, customHeaders ]])", "launch a webserver with stats on that address with that password" },
420 { "whashed", false, "", "Weighted hashed ('sticky') distribution over available servers, based on the server 'weight' parameter" },
421 { "wrandom", false, "", "Weighted random over available servers, based on the server 'weight' parameter" },
422};
423
ffb07158 424extern "C" {
425char* my_generator(const char* text, int state)
426{
427 string t(text);
e8130431
RG
428 /* to keep it readable, we try to keep only 4 keywords per line
429 and to start a new line when the first letter changes */
ffb07158 430 static int s_counter=0;
431 int counter=0;
432 if(!state)
433 s_counter=0;
434
ca4252e0
RG
435 for(const auto& keyword : g_consoleKeywords) {
436 if(boost::starts_with(keyword.name, t) && counter++ == s_counter) {
437 std::string value(keyword.name);
ffb07158 438 s_counter++;
ca4252e0
RG
439 if (keyword.function) {
440 value += "(";
441 if (keyword.parameters.empty()) {
442 value += ")";
443 }
444 }
445 return strdup(value.c_str());
ffb07158 446 }
447 }
448 return 0;
449}
450
451char** my_completion( const char * text , int start, int end)
452{
453 char **matches=0;
454 if (start == 0)
455 matches = rl_completion_matches ((char*)text, &my_generator);
d9de8b61
CH
456
457 // skip default filename completion.
458 rl_attempted_completion_over = 1;
459
ffb07158 460 return matches;
461}
462}
463
464void controlClientThread(int fd, ComboAddress client)
465try
466{
9c186303 467 setTCPNoDelay(fd);
333ea16e 468 SodiumNonce theirs, ours, readingNonce, writingNonce;
ffb07158 469 ours.init();
333ea16e 470 readn2(fd, (char*)theirs.value, sizeof(theirs.value));
ffb07158 471 writen2(fd, (char*)ours.value, sizeof(ours.value));
333ea16e
RG
472 readingNonce.merge(ours, theirs);
473 writingNonce.merge(theirs, ours);
ffb07158 474
475 for(;;) {
476 uint32_t len;
477 if(!getMsgLen32(fd, &len))
478 break;
0877da98
RG
479
480 if (len == 0) {
481 /* just ACK an empty message
482 with an empty response */
483 putMsgLen32(fd, 0);
484 continue;
485 }
486
ffb07158 487 boost::scoped_array<char> msg(new char[len]);
488 readn2(fd, msg.get(), len);
489
490 string line(msg.get(), len);
333ea16e 491 line = sodDecryptSym(line, g_key, readingNonce);
ffb07158 492 // cerr<<"Have decrypted line: "<<line<<endl;
493 string response;
494 try {
7e7a5b71 495 bool withReturn=true;
496 retry:;
497 try {
498 std::lock_guard<std::mutex> lock(g_luamutex);
499
500 g_outputBuffer.clear();
501 resetLuaSideEffect();
502 auto ret=g_lua.executeCode<
503 boost::optional<
504 boost::variant<
505 string,
506 shared_ptr<DownstreamState>,
507 std::unordered_map<string, double>
508 >
509 >
510 >(withReturn ? ("return "+line) : line);
ffb07158 511
512 if(ret) {
af619119
RG
513 if (const auto dsValue = boost::get<shared_ptr<DownstreamState>>(&*ret)) {
514 response=(*dsValue)->getName()+"\n";
ffb07158 515 }
516 else if (const auto strValue = boost::get<string>(&*ret)) {
7e7a5b71 517 response=*strValue+"\n";
ffb07158 518 }
7e7a5b71 519 else if(const auto um = boost::get<std::unordered_map<string, double> >(&*ret)) {
520 using namespace json11;
521 Json::object o;
522 for(const auto& v : *um)
523 o[v.first]=v.second;
524 Json out = o;
525 response=out.dump()+"\n";
526 }
ffb07158 527 }
528 else
529 response=g_outputBuffer;
f758857a 530 if(!getLuaNoSideEffect())
531 feedConfigDelta(line);
7e7a5b71 532 }
533 catch(const LuaContext::SyntaxErrorException&) {
534 if(withReturn) {
535 withReturn=false;
536 goto retry;
537 }
538 throw;
539 }
ffb07158 540 }
628377ec
RG
541 catch(const LuaContext::WrongTypeException& e) {
542 response = "Command returned an object we can't print: " +std::string(e.what()) + "\n";
543 // tried to return something we don't understand
544 }
ffb07158 545 catch(const LuaContext::ExecutionErrorException& e) {
98bac6bc 546 if(!strcmp(e.what(),"invalid key to 'next'"))
547 response = "Error: Parsing function parameters, did you forget parameter name?";
548 else
549 response = "Error: " + string(e.what());
ffb07158 550 try {
551 std::rethrow_if_nested(e);
af619119
RG
552 } catch(const std::exception& ne) {
553 // ne is the exception that was thrown from inside the lambda
554 response+= ": " + string(ne.what());
ffb07158 555 }
af619119
RG
556 catch(const PDNSException& ne) {
557 // ne is the exception that was thrown from inside the lambda
558 response += ": " + string(ne.reason);
ffb07158 559 }
560 }
561 catch(const LuaContext::SyntaxErrorException& e) {
562 response = "Error: " + string(e.what()) + ": ";
563 }
333ea16e 564 response = sodEncryptSym(response, g_key, writingNonce);
ffb07158 565 putMsgLen32(fd, response.length());
566 writen2(fd, response.c_str(), response.length());
567 }
568 infolog("Closed control connection from %s", client.toStringWithPort());
569 close(fd);
570 fd=-1;
571}
572catch(std::exception& e)
573{
574 errlog("Got an exception in client connection from %s: %s", client.toStringWithPort(), e.what());
575 if(fd >= 0)
576 close(fd);
577}
578