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