• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PowerDNS / pdns / 13012068652

28 Jan 2025 01:59PM UTC coverage: 64.71% (+0.01%) from 64.699%
13012068652

Pull #14724

github

web-flow
Merge b15562560 into db18c3a17
Pull Request #14724: dnsdist: Add meson support

38328 of 90334 branches covered (42.43%)

Branch coverage included in aggregate %.

361 of 513 new or added lines in 35 files covered. (70.37%)

42 existing lines in 13 files now uncovered.

128150 of 166934 relevant lines covered (76.77%)

4540890.91 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

30.71
/pdns/dnsdistdist/dnsdist-console.cc
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
 */
22
#include "config.h"
23

24
#include <fstream>
25
// we need this to get the home directory of the current user
26
#include <pwd.h>
27
#include <thread>
28

29
#ifdef HAVE_LIBEDIT
30
#if defined(__OpenBSD__) || defined(__NetBSD__)
31
// If this is not undeffed, __attribute__ will be redefined by /usr/include/readline/rlstdc.h
32
#undef __STRICT_ANSI__
33
#include <readline/readline.h>
34
#include <readline/history.h>
35
#else
36
#include <editline/readline.h>
37
#endif
38
#endif /* HAVE_LIBEDIT */
39

40
#include "ext/json11/json11.hpp"
41

42
#include "connection-management.hh"
43
#include "dolog.hh"
44
#include "dnsdist.hh"
45
#include "dnsdist-console.hh"
46
#include "dnsdist-crypto.hh"
47
#include "dnsdist-lua.hh"
48
#include "threadname.hh"
49

50
static LockGuarded<std::vector<pair<timeval, string>>> s_confDelta;
51

52
static ConcurrentConnectionManager s_connManager(100);
53

54
class ConsoleConnection
55
{
56
public:
57
  ConsoleConnection(const ComboAddress& client, FDWrapper&& fileDesc) :
58
    d_client(client), d_fileDesc(std::move(fileDesc))
59
  {
276✔
60
    if (!s_connManager.registerConnection()) {
276✔
61
      throw std::runtime_error("Too many concurrent console connections");
1✔
62
    }
1✔
63
  }
276✔
64
  ConsoleConnection(ConsoleConnection&& rhs) noexcept :
65
    d_client(rhs.d_client), d_fileDesc(std::move(rhs.d_fileDesc))
66
  {
275✔
67
  }
275✔
68

69
  ConsoleConnection(const ConsoleConnection&) = delete;
70
  ConsoleConnection& operator=(const ConsoleConnection&) = delete;
71
  ConsoleConnection& operator=(ConsoleConnection&&) = delete;
72

73
  ~ConsoleConnection()
74
  {
550✔
75
    if (d_fileDesc.getHandle() != -1) {
550✔
76
      s_connManager.releaseConnection();
275✔
77
    }
275✔
78
  }
550✔
79

80
  [[nodiscard]] int getFD() const
81
  {
2,188✔
82
    return d_fileDesc.getHandle();
2,188✔
83
  }
2,188✔
84

85
  [[nodiscard]] const ComboAddress& getClient() const
86
  {
275✔
87
    return d_client;
275✔
88
  }
275✔
89

90
private:
91
  ComboAddress d_client;
92
  FDWrapper d_fileDesc;
93
};
94

95
static void feedConfigDelta(const std::string& line)
96
{
102✔
97
  if (line.empty()) {
102!
98
    return;
×
99
  }
×
100
  timeval now{};
102✔
101
  gettimeofday(&now, nullptr);
102✔
102
  s_confDelta.lock()->emplace_back(now, line);
102✔
103
}
102✔
104

105
namespace dnsdist::console
106
{
107
const std::vector<std::pair<timeval, std::string>>& getConfigurationDelta()
108
{
×
109
  return *(s_confDelta.lock());
×
110
}
×
111
}
112

113
#ifdef HAVE_LIBEDIT
114
static string historyFile(const bool& ignoreHOME = false)
115
{
×
116
  string ret;
×
117

118
  passwd pwd{};
×
119
  passwd* result{nullptr};
×
120
  std::array<char, 16384> buf{};
×
121
  getpwuid_r(geteuid(), &pwd, buf.data(), buf.size(), &result);
×
122

123
  // NOLINTNEXTLINE(concurrency-mt-unsafe): we are not modifying the environment
124
  const char* homedir = getenv("HOME");
×
125
  if (result != nullptr) {
×
126
    ret = string(pwd.pw_dir);
×
127
  }
×
128
  if (homedir != nullptr && !ignoreHOME) { // $HOME overrides what the OS tells us
×
129
    ret = string(homedir);
×
130
  }
×
131
  if (ret.empty()) {
×
132
    ret = "."; // CWD if nothing works..
×
133
  }
×
134
  ret.append("/.dnsdist_history");
×
135
  return ret;
×
136
}
×
137
#endif /* HAVE_LIBEDIT */
138

139
enum class ConsoleCommandResult : uint8_t
140
{
141
  Valid = 0,
142
  ConnectionClosed,
143
  TooLarge
144
};
145

146
static ConsoleCommandResult getMsgLen32(int fileDesc, uint32_t* len)
147
{
546✔
148
  try {
546✔
149
    uint32_t raw{0};
546✔
150
    size_t ret = readn2(fileDesc, &raw, sizeof(raw));
546✔
151

152
    if (ret != sizeof raw) {
546!
153
      return ConsoleCommandResult::ConnectionClosed;
×
154
    }
×
155

156
    *len = ntohl(raw);
546✔
157
    if (*len > dnsdist::configuration::getCurrentRuntimeConfiguration().d_consoleOutputMsgMaxSize) {
546!
158
      return ConsoleCommandResult::TooLarge;
×
159
    }
×
160

161
    return ConsoleCommandResult::Valid;
546✔
162
  }
546✔
163
  catch (...) {
546✔
164
    return ConsoleCommandResult::ConnectionClosed;
273✔
165
  }
273✔
166
}
546✔
167

168
static bool putMsgLen32(int fileDesc, uint32_t len)
169
{
273✔
170
  try {
273✔
171
    uint32_t raw = htonl(len);
273✔
172
    size_t ret = writen2(fileDesc, &raw, sizeof raw);
273✔
173
    return ret == sizeof raw;
273✔
174
  }
273✔
175
  catch (...) {
273✔
176
    return false;
×
177
  }
×
178
}
273✔
179

180
static ConsoleCommandResult sendMessageToServer(int fileDesc, const std::string& line, dnsdist::crypto::authenticated::Nonce& readingNonce, dnsdist::crypto::authenticated::Nonce& writingNonce, const bool outputEmptyLine)
181
{
×
182
  const auto& consoleKey = dnsdist::configuration::getCurrentRuntimeConfiguration().d_consoleKey;
×
183
  string msg = dnsdist::crypto::authenticated::encryptSym(line, consoleKey, writingNonce);
×
184
  const auto msgLen = msg.length();
×
185
  if (msgLen > std::numeric_limits<uint32_t>::max()) {
×
186
    cerr << "Encrypted message is too long to be sent to the server, " << std::to_string(msgLen) << " > " << std::numeric_limits<uint32_t>::max() << endl;
×
187
    return ConsoleCommandResult::TooLarge;
×
188
  }
×
189

190
  putMsgLen32(fileDesc, static_cast<uint32_t>(msgLen));
×
191

192
  if (!msg.empty()) {
×
193
    writen2(fileDesc, msg);
×
194
  }
×
195

196
  uint32_t len{0};
×
197
  auto commandResult = getMsgLen32(fileDesc, &len);
×
198
  if (commandResult == ConsoleCommandResult::ConnectionClosed) {
×
199
    cout << "Connection closed by the server." << endl;
×
200
    return commandResult;
×
201
  }
×
202
  if (commandResult == ConsoleCommandResult::TooLarge) {
×
203
    cerr << "Received a console message whose length (" << len << ") is exceeding the allowed one (" << dnsdist::configuration::getCurrentRuntimeConfiguration().d_consoleOutputMsgMaxSize << "), closing that connection" << endl;
×
204
    return commandResult;
×
205
  }
×
206

207
  if (len == 0) {
×
208
    if (outputEmptyLine) {
×
209
      cout << endl;
×
210
    }
×
211

212
    return ConsoleCommandResult::Valid;
×
213
  }
×
214

215
  msg.clear();
×
216
  msg.resize(len);
×
217
  readn2(fileDesc, msg.data(), len);
×
218
  msg = dnsdist::crypto::authenticated::decryptSym(msg, consoleKey, readingNonce);
×
219
  cout << msg;
×
220
  cout.flush();
×
221

222
  return ConsoleCommandResult::Valid;
×
223
}
×
224

225
namespace dnsdist::console
226
{
227
void doClient(const std::string& command)
228
{
×
229
  //coverity[auto_causes_copy]
230
  const auto consoleKey = dnsdist::configuration::getCurrentRuntimeConfiguration().d_consoleKey;
×
231
  //coverity[auto_causes_copy]
232
  const auto server = dnsdist::configuration::getCurrentRuntimeConfiguration().d_consoleServerAddress;
×
233
  if (!dnsdist::crypto::authenticated::isValidKey(consoleKey)) {
×
234
    cerr << "The currently configured console key is not valid, please configure a valid key using the setKey() directive" << endl;
×
235
    return;
×
236
  }
×
237

238
  if (dnsdist::configuration::getCurrentRuntimeConfiguration().d_verbose) {
×
239
    cout << "Connecting to " << server.toStringWithPort() << endl;
×
240
  }
×
241

242
  auto fileDesc = FDWrapper(socket(server.sin4.sin_family, SOCK_STREAM, 0));
×
243
  if (fileDesc.getHandle() < 0) {
×
244
    cerr << "Unable to connect to " << server.toStringWithPort() << endl;
×
245
    return;
×
246
  }
×
247
  SConnect(fileDesc.getHandle(), server);
×
248
  setTCPNoDelay(fileDesc.getHandle());
×
249
  dnsdist::crypto::authenticated::Nonce theirs;
×
250
  dnsdist::crypto::authenticated::Nonce ours;
×
251
  dnsdist::crypto::authenticated::Nonce readingNonce;
×
252
  dnsdist::crypto::authenticated::Nonce writingNonce;
×
253
  ours.init();
×
254

255
  writen2(fileDesc.getHandle(), ours.value.data(), ours.value.size());
×
256
  readn2(fileDesc.getHandle(), theirs.value.data(), theirs.value.size());
×
257
  readingNonce.merge(ours, theirs);
×
258
  writingNonce.merge(theirs, ours);
×
259

260
  /* try sending an empty message, the server should send an empty
261
     one back. If it closes the connection instead, we are probably
262
     having a key mismatch issue. */
263
  auto commandResult = sendMessageToServer(fileDesc.getHandle(), "", readingNonce, writingNonce, false);
×
264
  if (commandResult == ConsoleCommandResult::ConnectionClosed) {
×
265
    cerr << "The server closed the connection right away, likely indicating a key mismatch. Please check your setKey() directive." << endl;
×
266
    return;
×
267
  }
×
268
  if (commandResult == ConsoleCommandResult::TooLarge) {
×
269
    return;
×
270
  }
×
271

272
  if (!command.empty()) {
×
273
    sendMessageToServer(fileDesc.getHandle(), command, readingNonce, writingNonce, false);
×
274
    return;
×
275
  }
×
276

277
#ifdef HAVE_LIBEDIT
×
278
  string histfile = historyFile();
×
279
  {
×
280
    ifstream history(histfile);
×
281
    string line;
×
282
    while (getline(history, line)) {
×
283
      add_history(line.c_str());
×
284
    }
×
285
  }
×
286
  ofstream history(histfile, std::ios_base::app);
×
287
  string lastline;
×
288
  for (;;) {
×
289
    char* sline = readline("> ");
×
290
    rl_bind_key('\t', rl_complete);
×
291
    if (sline == nullptr) {
×
292
      break;
×
293
    }
×
294

295
    string line(sline);
×
296
    if (!line.empty() && line != lastline) {
×
297
      add_history(sline);
×
298
      history << sline << endl;
×
299
      history.flush();
×
300
    }
×
301
    lastline = line;
×
302
    // NOLINTNEXTLINE(cppcoreguidelines-no-malloc,cppcoreguidelines-owning-memory): readline
303
    free(sline);
×
304

305
    if (line == "quit") {
×
306
      break;
×
307
    }
×
308
    if (line == "help" || line == "?") {
×
309
      line = "help()";
×
310
    }
×
311

312
    /* no need to send an empty line to the server */
313
    if (line.empty()) {
×
314
      continue;
×
315
    }
×
316

317
    commandResult = sendMessageToServer(fileDesc.getHandle(), line, readingNonce, writingNonce, true);
×
318
    if (commandResult != ConsoleCommandResult::Valid) {
×
319
      break;
×
320
    }
×
321
  }
×
322
#else
323
  errlog("Client mode requested but libedit support is not available");
324
#endif /* HAVE_LIBEDIT */
325
}
×
326

327
#ifdef HAVE_LIBEDIT
328
static std::optional<std::string> getNextConsoleLine(ofstream& history, std::string& lastline)
329
{
×
330
  char* sline = readline("> ");
×
331
  rl_bind_key('\t', rl_complete);
×
332
  if (sline == nullptr) {
×
333
    return std::nullopt;
×
334
  }
×
335

336
  string line(sline);
×
337
  if (!line.empty() && line != lastline) {
×
338
    add_history(sline);
×
339
    history << sline << endl;
×
340
    history.flush();
×
341
  }
×
342

343
  lastline = line;
×
344
  // NOLINTNEXTLINE(cppcoreguidelines-no-malloc,cppcoreguidelines-owning-memory): readline
345
  free(sline);
×
346

347
  return line;
×
348
}
×
349
#else /* HAVE_LIBEDIT */
350
static std::optional<std::string> getNextConsoleLine()
351
{
352
  std::string line;
353
  if (!std::getline(std::cin, line)) {
354
    return std::nullopt;
355
  }
356
  return line;
357
}
358
#endif /* HAVE_LIBEDIT */
359

360
void doConsole()
361
{
×
362
#ifdef HAVE_LIBEDIT
×
363
  string histfile = historyFile(true);
×
364
  {
×
365
    ifstream history(histfile);
×
366
    string line;
×
367
    while (getline(history, line)) {
×
368
      add_history(line.c_str());
×
369
    }
×
370
  }
×
371
  ofstream history(histfile, std::ios_base::app);
×
372
  string lastline;
×
373
#endif /* HAVE_LIBEDIT */
×
374

375
  for (;;) {
×
376
#ifdef HAVE_LIBEDIT
×
377
    auto line = getNextConsoleLine(history, lastline);
×
378
#else /* HAVE_LIBEDIT */
379
    auto line = getNextConsoleLine();
380
#endif /* HAVE_LIBEDIT */
381
    if (!line) {
×
382
      break;
×
383
    }
×
384

385
    if (*line == "quit") {
×
386
      break;
×
387
    }
×
388
    if (*line == "help" || *line == "?") {
×
389
      line = "help()";
×
390
    }
×
391

392
    string response;
×
393
    try {
×
394
      bool withReturn = true;
×
395
    retry:;
×
396
      try {
×
397
        auto lua = g_lua.lock();
×
398
        g_outputBuffer.clear();
×
399
        resetLuaSideEffect();
×
400
        auto ret = lua->executeCode<
×
401
          boost::optional<
×
402
            boost::variant<
×
403
              string,
×
404
              shared_ptr<DownstreamState>,
×
405
              ClientState*,
×
406
              std::unordered_map<string, double>>>>(withReturn ? ("return " + *line) : *line);
×
407
        if (ret) {
×
408
          if (const auto* dsValue = boost::get<shared_ptr<DownstreamState>>(&*ret)) {
×
409
            if (*dsValue) {
×
410
              cout << (*dsValue)->getName() << endl;
×
411
            }
×
412
          }
×
413
          else if (const auto* csValue = boost::get<ClientState*>(&*ret)) {
×
414
            if (*csValue != nullptr) {
×
415
              cout << (*csValue)->local.toStringWithPort() << endl;
×
416
            }
×
417
          }
×
418
          else if (const auto* strValue = boost::get<string>(&*ret)) {
×
419
            cout << *strValue << endl;
×
420
          }
×
421
          else if (const auto* mapValue = boost::get<std::unordered_map<string, double>>(&*ret)) {
×
422
            using namespace json11;
×
423
            Json::object obj;
×
424
            for (const auto& value : *mapValue) {
×
425
              obj[value.first] = value.second;
×
426
            }
×
427
            Json out = obj;
×
428
            cout << out.dump() << endl;
×
429
          }
×
430
        }
×
431
        else {
×
432
          cout << g_outputBuffer << std::flush;
×
433
        }
×
434

435
        if (!getLuaNoSideEffect()) {
×
436
          feedConfigDelta(*line);
×
437
        }
×
438
      }
×
439
      catch (const LuaContext::SyntaxErrorException&) {
×
440
        if (withReturn) {
×
441
          withReturn = false;
×
442
          // NOLINTNEXTLINE(cppcoreguidelines-avoid-goto)
443
          goto retry;
×
444
        }
×
445
        throw;
×
446
      }
×
447
    }
×
448
    catch (const LuaContext::WrongTypeException& e) {
×
449
      std::cerr << "Command returned an object we can't print: " << std::string(e.what()) << std::endl;
×
450
      // tried to return something we don't understand
451
    }
×
452
    catch (const LuaContext::ExecutionErrorException& e) {
×
453
      if (strcmp(e.what(), "invalid key to 'next'") == 0) {
×
454
        std::cerr << "Error parsing parameters, did you forget parameter name?";
×
455
      }
×
456
      else {
×
457
        std::cerr << e.what();
×
458
      }
×
459

460
      try {
×
461
        std::rethrow_if_nested(e);
×
462

463
        std::cerr << std::endl;
×
464
      }
×
465
      catch (const std::exception& ne) {
×
466
        // ne is the exception that was thrown from inside the lambda
467
        std::cerr << ": " << ne.what() << std::endl;
×
468
      }
×
469
      catch (const PDNSException& ne) {
×
470
        // ne is the exception that was thrown from inside the lambda
471
        std::cerr << ": " << ne.reason << std::endl;
×
472
      }
×
473
    }
×
474
    catch (const std::exception& e) {
×
475
      std::cerr << e.what() << std::endl;
×
476
    }
×
477
  }
×
478
}
×
479
}
480

481
#ifndef DISABLE_COMPLETION
482
/**** CARGO CULT CODE AHEAD ****/
483
static const std::vector<dnsdist::console::ConsoleKeyword> s_consoleKeywords
484
{
485
  /* keyword, function, parameters, description */
486
  {"addACL", true, "netmask", "add to the ACL set who can use this server"},
487
    {"addAction", true, R"(DNS rule, DNS action [, {uuid="UUID", name="name"}])", "add a rule"},
488
    {"addBPFFilterDynBlocks", true, "addresses, dynbpf[[, seconds=10], msg]", "This is the eBPF equivalent of addDynBlocks(), blocking a set of addresses for (optionally) a number of seconds, using an eBPF dynamic filter"},
489
    {"addCapabilitiesToRetain", true, "capability or list of capabilities", "Linux capabilities to retain after startup, like CAP_BPF"},
490
    {"addConsoleACL", true, "netmask", "add a netmask to the console ACL"},
491
    {"addDNSCryptBind", true, R"('127.0.0.1:8443", "provider name", "/path/to/resolver.cert", "/path/to/resolver.key", {reusePort=false, tcpFastOpenQueueSize=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"},
492
    {"addDOHLocal", true, "addr, certFile, keyFile [, urls [, vars]]", "listen to incoming DNS over HTTPS queries on the specified address using the specified certificate and key. The last two parameters are tables"},
493
    {"addDOH3Local", true, "addr, certFile, keyFile [, vars]", "listen to incoming DNS over HTTP/3 queries on the specified address using the specified certificate and key. The last parameter is a table"},
494
    {"addDOQLocal", true, "addr, certFile, keyFile [, vars]", "listen to incoming DNS over QUIC queries on the specified address using the specified certificate and key. The last parameter is a table"},
495
    {"addDynamicBlock", true, "address, message[, action [, seconds [, clientIPMask [, clientIPPortMask]]]]", "block the supplied address with message `msg`, for `seconds` seconds (10 by default), applying `action` (default to the one set with `setDynBlocksAction()`)"},
496
    {"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()`)"},
497
    {"addDynBlockSMT", true, "names, message[, seconds [, action]]", "block the set of names with message `msg`, for `seconds` seconds (10 by default), applying `action` (default to the one set with `setDynBlocksAction()`)"},
498
    {"addLocal", true, R"(addr [, {doTCP=true, reusePort=false, tcpFastOpenQueueSize=0, interface="", cpus={}}])", "add `addr` to the list of addresses we listen on"},
499
    {"addCacheHitResponseAction", true, R"(DNS rule, DNS response action [, {uuid="UUID", name="name"}}])", "add a cache hit response rule"},
500
    {"addCacheInsertedResponseAction", true, R"(DNS rule, DNS response action [, {uuid="UUID", name="name"}}])", "add a cache inserted response rule"},
501
    {"addMaintenanceCallback", true, "callback", "register a function to be called as part of the maintenance hook, every second"},
502
    {"addResponseAction", true, R"(DNS rule, DNS response action [, {uuid="UUID", name="name"}}])", "add a response rule"},
503
    {"addSelfAnsweredResponseAction", true, R"(DNS rule, DNS response action [, {uuid="UUID", name="name"}}])", "add a self-answered response rule"},
504
    {"addXFRResponseAction", true, R"(DNS rule, DNS response action [, {uuid="UUID", name="name"}}])", "add a XFR response rule"},
505
    {"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"},
506
    {"AllowAction", true, "", "let these packets go through"},
507
    {"AllowResponseAction", true, "", "let these packets go through"},
508
    {"AllRule", true, "", "matches all traffic"},
509
    {"AndRule", true, "list of DNS rules", "matches if all sub-rules matches"},
510
    {"benchRule", true, "DNS Rule [, iterations [, suffix]]", "bench the specified DNS rule"},
511
    {"carbonServer", true, "serverIP, [ourname], [interval]", "report statistics to serverIP using our hostname, or 'ourname' if provided, every 'interval' seconds"},
512
    {"clearConsoleHistory", true, "", "clear the internal (in-memory) history of console commands"},
513
    {"clearDynBlocks", true, "", "clear all dynamic blocks"},
514
    {"clearQueryCounters", true, "", "clears the query counter buffer"},
515
    {"clearRules", true, "", "remove all current rules"},
516
    {"controlSocket", true, "addr", "open a control socket on this address / connect to this address in client mode"},
517
    {"ContinueAction", true, "action", "execute the specified action and continue the processing of the remaining rules, regardless of the return of the action"},
518
    {"declareMetric", true, "name, type, description [, prometheusName]", "Declare a custom metric"},
519
    {"decMetric", true, "name", "Decrement a custom metric"},
520
    {"DelayAction", true, "milliseconds", "delay the response by the specified amount of milliseconds (UDP-only)"},
521
    {"DelayResponseAction", true, "milliseconds", "delay the response by the specified amount of milliseconds (UDP-only)"},
522
    {"delta", true, "", "shows all commands entered that changed the configuration"},
523
    {"DNSSECRule", true, "", "matches queries with the DO bit set"},
524
    {"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"},
525
    {"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"},
526
    {"DropAction", true, "", "drop these packets"},
527
    {"DropResponseAction", true, "", "drop these packets"},
528
    {"DSTPortRule", true, "port", "matches questions received to the destination port specified"},
529
    {"dumpStats", true, "", "print all statistics we gather"},
530
    {"dynBlockRulesGroup", true, "", "return a new DynBlockRulesGroup object"},
531
    {"EDNSVersionRule", true, "version", "matches queries with the specified EDNS version"},
532
    {"EDNSOptionRule", true, "optcode", "matches queries with the specified EDNS0 option present"},
533
    {"ERCodeAction", true, "ercode", "Reply immediately by turning the query into a response with the specified EDNS extended rcode"},
534
    {"ERCodeRule", true, "rcode", "matches responses with the specified extended rcode (EDNS0)"},
535
    {"exceedNXDOMAINs", true, "rate, seconds", "get set of addresses that exceed `rate` NXDOMAIN/s over `seconds` seconds"},
536
    {"exceedQRate", true, "rate, seconds", "get set of address that exceed `rate` queries/s over `seconds` seconds"},
537
    {"exceedQTypeRate", true, "type, rate, seconds", "get set of address that exceed `rate` queries/s for queries of type `type` over `seconds` seconds"},
538
    {"exceedRespByterate", true, "rate, seconds", "get set of addresses that exceeded `rate` bytes/s answers over `seconds` seconds"},
539
    {"exceedServFails", true, "rate, seconds", "get set of addresses that exceed `rate` servfails/s over `seconds` seconds"},
540
    {"firstAvailable", false, "", "picks the server with the lowest `order` that has not exceeded its QPS limit"},
541
    {"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"},
542
    {"generateDNSCryptCertificate", true, R"("/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"},
543
    {"generateDNSCryptProviderKeys", true, R"("/path/to/providerPublic.key", "/path/to/providerPrivate.key")", "generate a new provider keypair"},
544
    {"getAction", true, "n", "Returns the Action associated with rule n"},
545
    {"getBind", true, "n", "returns the listener at index n"},
546
    {"getBindCount", true, "", "returns the number of listeners all kinds"},
547
    {"getCacheHitResponseRule", true, "selector", "Return the cache-hit response rule corresponding to the selector, if any"},
548
    {"getCacheInsertedResponseRule", true, "selector", "Return the cache-inserted response rule corresponding to the selector, if any"},
549
    {"getCurrentTime", true, "", "returns the current time"},
550
    {"getDynamicBlocks", true, "", "returns a table of the current network-based dynamic blocks"},
551
    {"getDynamicBlocksSMT", true, "", "returns a table of the current suffix-based dynamic blocks"},
552
    {"getDNSCryptBind", true, "n", "return the `DNSCryptContext` object corresponding to the bind `n`"},
553
    {"getDNSCryptBindCount", true, "", "returns the number of DNSCrypt listeners"},
554
    {"getDOHFrontend", true, "n", "returns the DoH frontend with index n"},
555
    {"getDOHFrontendCount", true, "", "returns the number of DoH listeners"},
556
    {"getDOH3Frontend", true, "n", "returns the DoH3 frontend with index n"},
557
    {"getDOH3FrontendCount", true, "", "returns the number of DoH3 listeners"},
558
    {"getDOQFrontend", true, "n", "returns the DoQ frontend with index n"},
559
    {"getDOQFrontendCount", true, "", "returns the number of DoQ listeners"},
560
    {"getListOfAddressesOfNetworkInterface", true, "itf", "returns the list of addresses configured on a given network interface, as strings"},
561
    {"getListOfNetworkInterfaces", true, "", "returns the list of network interfaces present on the system, as strings"},
562
    {"getListOfRangesOfNetworkInterface", true, "itf", "returns the list of network ranges configured on a given network interface, as strings"},
563
    {"getMACAddress", true, "IP addr", "return the link-level address (MAC) corresponding to the supplied neighbour  IP address, if known by the kernel"},
564
    {"getMetric", true, "name", "Get the value of a custom metric"},
565
    {"getOutgoingTLSSessionCacheSize", true, "", "returns the number of TLS sessions (for outgoing connections) currently cached"},
566
    {"getPool", true, "name", "return the pool named `name`, or \"\" for the default pool"},
567
    {"getPoolServers", true, "pool", "return servers part of this pool"},
568
    {"getPoolNames", true, "", "returns a table with all the pool names"},
569
    {"getQueryCounters", true, "[max=10]", "show current buffer of query counters, limited by 'max' if provided"},
570
    {"getResponseRing", true, "", "return the current content of the response ring"},
571
    {"getResponseRule", true, "selector", "Return the response rule corresponding to the selector, if any"},
572
    {"getRespRing", true, "", "return the qname/rcode content of the response ring"},
573
    {"getRule", true, "selector", "Return the rule corresponding to the selector, if any"},
574
    {"getSelfAnsweredResponseRule", true, "selector", "Return the self-answered response rule corresponding to the selector, if any"},
575
    {"getServer", true, "id", "returns server with index 'n' or whose uuid matches if 'id' is an UUID string"},
576
    {"getServers", true, "", "returns a table with all defined servers"},
577
    {"getStatisticsCounters", true, "", "returns a map of statistic counters"},
578
    {"getTopCacheHitResponseRules", true, "[top]", "return the `top` cache-hit response rules"},
579
    {"getTopCacheInsertedResponseRules", true, "[top]", "return the `top` cache-inserted response rules"},
580
    {"getTopResponseRules", true, "[top]", "return the `top` response rules"},
581
    {"getTopRules", true, "[top]", "return the `top` rules"},
582
    {"getTopSelfAnsweredResponseRules", true, "[top]", "return the `top` self-answered response rules"},
583
    {"getTopXFRResponseRules", true, "[top]", "return the `top` XFR response rules"},
584
    {"getTLSFrontend", true, "n", "returns the TLS frontend with index n"},
585
    {"getTLSFrontendCount", true, "", "returns the number of DoT listeners"},
586
    {"getVerbose", true, "", "get whether log messages at the verbose level will be logged"},
587
    {"getXFRResponseRule", true, "selector", "Return the XFR response rule corresponding to the selector, if any"},
588
    {"grepq", true, R"(Netmask|DNS Name|100ms|{"::1", "powerdns.com", "100ms"} [, n] [,options])", "shows the last n queries and responses matching the specified client address or range (Netmask), or the specified DNS Name, or slower than 100ms"},
589
    {"hashPassword", true, "password [, workFactor]", "Returns a hashed and salted version of the supplied password, usable with 'setWebserverConfig()'"},
590
    {"HTTPHeaderRule", true, "name, regex", "matches DoH queries with a HTTP header 'name' whose content matches the regular expression 'regex'"},
591
    {"HTTPPathRegexRule", true, "regex", "matches DoH queries whose HTTP path matches 'regex'"},
592
    {"HTTPPathRule", true, "path", "matches DoH queries whose HTTP path is an exact match to 'path'"},
593
    {"HTTPStatusAction", true, "status, reason, body", "return an HTTP response"},
594
    {"inClientStartup", true, "", "returns true during console client parsing of configuration"},
595
    {"includeDirectory", true, "path", "include configuration files from `path`"},
596
    {"incMetric", true, "name", "Increment a custom metric"},
597
    {"KeyValueLookupKeyQName", true, "[wireFormat]", "Return a new KeyValueLookupKey object that, when passed to KeyValueStoreLookupAction or KeyValueStoreLookupRule, will return the qname of the query, either in wire format (default) or in plain text if 'wireFormat' is false"},
598
    {"KeyValueLookupKeySourceIP", true, "[v4Mask [, v6Mask [, includePort]]]", "Return a new KeyValueLookupKey object that, when passed to KeyValueStoreLookupAction or KeyValueStoreLookupRule, will return the (possibly bitmasked) source IP of the client in network byte-order."},
599
    {"KeyValueLookupKeySuffix", true, "[minLabels [,wireFormat]]", "Return a new KeyValueLookupKey object that, when passed to KeyValueStoreLookupAction or KeyValueStoreLookupRule, will return a vector of keys based on the labels of the qname in DNS wire format or plain text"},
600
    {"KeyValueLookupKeyTag", true, "tag", "Return a new KeyValueLookupKey object that, when passed to KeyValueStoreLookupAction or KeyValueStoreLookupRule, will return the value of the corresponding tag for this query, if it exists"},
601
    {"KeyValueStoreLookupAction", true, "kvs, lookupKey, destinationTag", "does a lookup into the key value store referenced by 'kvs' using the key returned by 'lookupKey', and storing the result if any into the tag named 'destinationTag'"},
602
    {"KeyValueStoreRangeLookupAction", true, "kvs, lookupKey, destinationTag", "does a range-based lookup into the key value store referenced by 'kvs' using the key returned by 'lookupKey', and storing the result if any into the tag named 'destinationTag'"},
603
    {"KeyValueStoreLookupRule", true, "kvs, lookupKey", "matches queries if the key is found in the specified Key Value store"},
604
    {"KeyValueStoreRangeLookupRule", true, "kvs, lookupKey", "matches queries if the key is found in the specified Key Value store"},
605
    {"leastOutstanding", false, "", "Send traffic to downstream server with least outstanding queries, with the lowest 'order', and within that the lowest recent latency"},
606
#if defined(HAVE_LIBSSL) && !defined(HAVE_TLS_PROVIDERS)
607
    {"loadTLSEngine", true, "engineName [, defaultString]", "Load the OpenSSL engine named 'engineName', setting the engine default string to 'defaultString' if supplied"},
608
#endif
609
#if defined(HAVE_LIBSSL) && OPENSSL_VERSION_MAJOR >= 3 && defined(HAVE_TLS_PROVIDERS)
610
    {"loadTLSProvider", true, "providerName", "Load the OpenSSL provider named 'providerName'"},
611
#endif
612
    {"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."},
613
    {"LogResponseAction", true, "[filename], [append], [buffered]", "Log a line for each response, to the specified file if any, to the console (require verbose) otherwise. 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."},
614
    {"LuaAction", true, "function", "Invoke a Lua function that accepts a DNSQuestion"},
615
    {"LuaFFIAction", true, "function", "Invoke a Lua FFI function that accepts a DNSQuestion"},
616
    {"LuaFFIPerThreadAction", true, "function", "Invoke a Lua FFI function that accepts a DNSQuestion, with a per-thread Lua context"},
617
    {"LuaFFIPerThreadResponseAction", true, "function", "Invoke a Lua FFI function that accepts a DNSResponse, with a per-thread Lua context"},
618
    {"LuaFFIResponseAction", true, "function", "Invoke a Lua FFI function that accepts a DNSResponse"},
619
    {"LuaFFIRule", true, "function", "Invoke a Lua FFI function that filters DNS questions"},
620
    {"LuaResponseAction", true, "function", "Invoke a Lua function that accepts a DNSResponse"},
621
    {"LuaRule", true, "function", "Invoke a Lua function that filters DNS questions"},
622
#ifdef HAVE_IPCIPHER
623
    {"makeIPCipherKey", true, "password", "generates a 16-byte key that can be used to pseudonymize IP addresses with IP cipher"},
624
#endif /* HAVE_IPCIPHER */
625
    {"makeKey", true, "", "generate a new server access key, emit configuration line ready for pasting"},
626
    {"makeRule", true, "rule", "Make a NetmaskGroupRule() or a SuffixMatchNodeRule(), depending on how it is called"},
627
    {"MaxQPSIPRule", true, "qps, [v4Mask=32 [, v6Mask=64 [, burst=qps [, expiration=300 [, cleanupDelay=60 [, scanFraction=10 [, shards=10]]]]]]]", "matches traffic exceeding the qps limit per subnet"},
628
    {"MaxQPSRule", true, "qps", "matches traffic **not** exceeding this qps limit"},
629
    {"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"},
630
    {"mvCacheHitResponseRuleToTop", true, "", "move the last cache hit response rule to the first position"},
631
    {"mvCacheInsertedResponseRule", true, "from, to", "move cache inserted response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule"},
632
    {"mvCacheInsertedResponseRuleToTop", true, "", "move the last cache inserted response rule to the first position"},
633
    {"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"},
634
    {"mvResponseRuleToTop", true, "", "move the last response rule to the first position"},
635
    {"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"},
636
    {"mvRuleToTop", true, "", "move the last rule to the first position"},
637
    {"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"},
638
    {"mvSelfAnsweredResponseRuleToTop", true, "", "move the last self-answered response rule to the first position"},
639
    {"mvXFRResponseRule", true, "from, to", "move XFR response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule"},
640
    {"mvXFRResponseRuleToTop", true, "", "move the last XFR response rule to the first position"},
641
    {"NetmaskGroupRule", true, "nmg[, src]", "Matches traffic from/to the network range specified in nmg. Set the src parameter to false to match nmg against destination address instead of source address. This can be used to differentiate between clients"},
642
    {"newBPFFilter", true, "{ipv4MaxItems=int, ipv4PinnedPath=string, ipv6MaxItems=int, ipv6PinnedPath=string, cidr4MaxItems=int, cidr4PinnedPath=string, cidr6MaxItems=int, cidr6PinnedPath=string, qnamesMaxItems=int, qnamesPinnedPath=string, external=bool}", "Return a new eBPF socket filter with specified options."},
643
    {"newCA", true, "address", "Returns a ComboAddress based on `address`"},
644
#ifdef HAVE_CDB
645
    {"newCDBKVStore", true, "fname, refreshDelay", "Return a new KeyValueStore object associated to the corresponding CDB database"},
646
#endif
647
    {"newDNSName", true, "name", "make a DNSName based on this .-terminated name"},
648
    {"newDNSNameSet", true, "", "returns a new DNSNameSet"},
649
    {"newDynBPFFilter", true, "bpf", "Return a new dynamic eBPF filter associated to a given BPF Filter"},
650
    {"newFrameStreamTcpLogger", true, "addr [, options]", "create a FrameStream logger object writing to a TCP address (addr should be ip:port), to use with `DnstapLogAction()` and `DnstapLogResponseAction()`"},
651
    {"newFrameStreamUnixLogger", true, "socket [, options]", "create a FrameStream logger object writing to a local unix socket, to use with `DnstapLogAction()` and `DnstapLogResponseAction()`"},
652
#ifdef HAVE_LMDB
653
    {"newLMDBKVStore", true, "fname, dbName [, noLock]", "Return a new KeyValueStore object associated to the corresponding LMDB database"},
654
#endif
655
    {"newNMG", true, "", "Returns a NetmaskGroup"},
656
    {"newPacketCache", true, "maxEntries[, maxTTL=86400, minTTL=0, temporaryFailureTTL=60, staleTTL=60, dontAge=false, numberOfShards=1, deferrableInsertLock=true, options={}]", "return a new Packet Cache"},
657
    {"newQPSLimiter", true, "rate, burst", "configure a QPS limiter with that rate and that burst capacity"},
658
    {"newRemoteLogger", true, "address:port [, timeout=2, maxQueuedEntries=100, reconnectWaitTime=1]", "create a Remote Logger object, to use with `RemoteLogAction()` and `RemoteLogResponseAction()`"},
659
    {"newRuleAction", true, R"(DNS rule, DNS action [, {uuid="UUID", name="name"}])", "return a pair of DNS Rule and DNS Action, to be used with `setRules()`"},
660
    {"newServer", true, R"({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, reconnectOnUp=false})", "instantiate a server"},
661
    {"newServerPolicy", true, "name, function", "create a policy object from a Lua function"},
662
    {"newSuffixMatchNode", true, "", "returns a new SuffixMatchNode"},
663
    {"newSVCRecordParameters", true, "priority, target, mandatoryParams, alpns, noDefaultAlpn [, port [, ech [, ipv4hints [, ipv6hints [, additionalParameters ]]]]]", "return a new SVCRecordParameters object, to use with SpoofSVCAction"},
664
    {"NegativeAndSOAAction", true, "nxd, zone, ttl, mname, rname, serial, refresh, retry, expire, minimum [, options]", "Turn a query into a NXDomain or NoData answer and sets a SOA record in the additional section"},
665
    {"NoneAction", true, "", "Does nothing. Subsequent rules are processed after this action"},
666
    {"NotRule", true, "selector", "Matches the traffic if the selector rule does not match"},
667
    {"OpcodeRule", true, "code", "Matches queries with opcode code. code can be directly specified as an integer, or one of the built-in DNSOpcodes"},
668
    {"OrRule", true, "selectors", "Matches the traffic if one or more of the the selectors rules does match"},
669
    {"PoolAction", true, "poolname [, stop]", "set the packet into the specified pool"},
670
    {"PoolAvailableRule", true, "poolname", "Check whether a pool has any servers available to handle queries"},
671
    {"PoolOutstandingRule", true, "poolname, limit", "Check whether a pool has outstanding queries above limit"},
672
    {"printDNSCryptProviderFingerprint", true, R"("/path/to/providerPublic.key")", "display the fingerprint of the provided resolver public key"},
673
    {"ProbaRule", true, "probability", "Matches queries with a given probability. 1.0 means always"},
674
    {"ProxyProtocolValueRule", true, "type [, value]", "matches queries with a specified Proxy Protocol TLV value of that type, optionally matching the content of the option as well"},
675
    {"QClassRule", true, "qclass", "Matches queries with the specified qclass. class can be specified as an integer or as one of the built-in DNSClass"},
676
    {"QNameLabelsCountRule", true, "min, max", "matches if the qname has less than `min` or more than `max` labels"},
677
    {"QNameRule", true, "qname", "matches queries with the specified qname"},
678
    {"QNameSetRule", true, "set", "Matches if the set contains exact qname"},
679
    {"QNameWireLengthRule", true, "min, max", "matches if the qname's length on the wire is less than `min` or more than `max` bytes"},
680
    {"QPSAction", true, "maxqps", "Drop a packet if it does exceed the maxqps queries per second limits. Letting the subsequent rules apply otherwise"},
681
    {"QPSPoolAction", true, "maxqps, poolname [, stop]", "Send the packet into the specified pool only if it does not exceed the maxqps queries per second limits. Letting the subsequent rules apply otherwise"},
682
    {"QTypeRule", true, "qtype", "matches queries with the specified qtype"},
683
    {"RCodeAction", true, "rcode", "Reply immediately by turning the query into a response with the specified rcode"},
684
    {"RCodeRule", true, "rcode", "matches responses with the specified rcode"},
685
    {"RDRule", true, "", "Matches queries with the RD flag set"},
686
    {"RecordsCountRule", true, "section, minCount, maxCount", "Matches if there is at least minCount and at most maxCount records in the section section. section can be specified as an integer or as a DNS Packet Sections"},
687
    {"RecordsTypeCountRule", true, "section, qtype, minCount, maxCount", "Matches if there is at least minCount and at most maxCount records of type type in the section section"},
688
    {"RegexRule", true, "regex", "matches the query name against the supplied regex"},
689
    {"registerDynBPFFilter", true, "DynBPFFilter", "register this dynamic BPF filter into the web interface so that its counters are displayed"},
690
    {"reloadAllCertificates", true, "", "reload all DNSCrypt and TLS certificates, along with their associated keys"},
691
    {"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."},
692
    {"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."},
693
    {"requestTCPStatesDump", true, "", "Request a dump of the TCP states (incoming connections, outgoing connections) during the next scan. Useful for debugging purposes only"},
694
    {"rmACL", true, "netmask", "remove netmask from ACL"},
695
    {"rmCacheHitResponseRule", true, "id", "remove cache hit response rule in position 'id', or whose uuid matches if 'id' is an UUID string, or finally whose name matches if 'id' is a string but not a valid UUID"},
696
    {"rmCacheInsertedResponseRule", true, "id", "remove cache inserted response rule in position 'id', or whose uuid matches if 'id' is an UUID string, or finally whose name matches if 'id' is a string but not a valid UUID"},
697
    {"rmResponseRule", true, "id", "remove response rule in position 'id', or whose uuid matches if 'id' is an UUID string, or finally whose name matches if 'id' is a string but not a valid UUID"},
698
    {"rmRule", true, "id", "remove rule in position 'id', or whose uuid matches if 'id' is an UUID string, or finally whose name matches if 'id' is a string but not a valid UUID"},
699
    {"rmSelfAnsweredResponseRule", true, "id", "remove self-answered response rule in position 'id', or whose uuid matches if 'id' is an UUID string, or finally whose name matches if 'id' is a string but not a valid UUID"},
700
    {"rmServer", true, "id", "remove server with index 'id' or whose uuid matches if 'id' is an UUID string"},
701
    {"rmXFRResponseRule", true, "id", "remove XFR response rule in position 'id', or whose uuid matches if 'id' is an UUID string, or finally whose name matches if 'id' is a string but not a valid UUID"},
702
    {"roundrobin", false, "", "Simple round robin over available servers"},
703
    {"sendCustomTrap", true, "str", "send a custom `SNMP` trap from Lua, containing the `str` string"},
704
    {"setACL", true, "{netmask, netmask}", "replace the ACL set with these netmasks. Use `setACL({})` to reset the list, meaning no one can use us"},
705
    {"setACLFromFile", true, "file", "replace the ACL set with netmasks in this file"},
706
    {"setAddEDNSToSelfGeneratedResponses", true, "add", "set whether to add EDNS to self-generated responses, provided that the initial query had EDNS"},
707
    {"setAllowEmptyResponse", true, "allow", "Set to true (defaults to false) to allow empty responses (qdcount=0) with a NoError or NXDomain rcode (default) from backends"},
708
    {"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"},
709
    {"setCacheCleaningDelay", true, "num", "Set the interval in seconds between two runs of the cache cleaning algorithm, removing expired entries"},
710
    {"setCacheCleaningPercentage", true, "num", "Set the percentage of the cache that the cache cleaning algorithm will try to free by removing expired entries. By default (100), all expired entries are remove"},
711
    {"setConsistentHashingBalancingFactor", true, "factor", "Set the balancing factor for bounded-load consistent hashing"},
712
    {"setConsoleACL", true, "{netmask, netmask}", "replace the console ACL set with these netmasks"},
713
    {"setConsoleConnectionsLogging", true, "enabled", "whether to log the opening and closing of console connections"},
714
    {"setConsoleMaximumConcurrentConnections", true, "max", "Set the maximum number of concurrent console connections"},
715
    {"setConsoleOutputMaxMsgSize", true, "messageSize", "set console message maximum size in bytes, default is 10 MB"},
716
    {"setDefaultBPFFilter", true, "filter", "When used at configuration time, the corresponding BPFFilter will be attached to every bind"},
717
    {"setDoHDownstreamCleanupInterval", true, "interval", "minimum interval in seconds between two cleanups of the idle DoH downstream connections"},
718
    {"setDoHDownstreamMaxIdleTime", true, "time", "Maximum time in seconds that a downstream DoH connection to a backend might stay idle"},
719
    {"setDynBlocksAction", true, "action", "set which action is performed when a query is blocked. Only DNSAction.Drop (the default) and DNSAction.Refused are supported"},
720
    {"setDynBlocksPurgeInterval", true, "sec", "set how often the expired dynamic block entries should be removed"},
721
    {"setDropEmptyQueries", true, "drop", "Whether to drop empty queries right away instead of sending a NOTIMP response"},
722
    {"setECSOverride", true, "bool", "whether to override an existing EDNS Client Subnet value in the query"},
723
    {"setECSSourcePrefixV4", true, "prefix-length", "the EDNS Client Subnet prefix-length used for IPv4 queries"},
724
    {"setECSSourcePrefixV6", true, "prefix-length", "the EDNS Client Subnet prefix-length used for IPv6 queries"},
725
    {"setKey", true, "key", "set access key to that key"},
726
    {"setLocal", true, R"(addr [, {doTCP=true, reusePort=false, tcpFastOpenQueueSize=0, interface="", cpus={}}])", "reset the list of addresses we listen on to this address"},
727
    {"setMaxCachedDoHConnectionsPerDownstream", true, "max", "Set the maximum number of inactive DoH connections to a backend cached by each worker DoH thread"},
728
    {"setMaxCachedTCPConnectionsPerDownstream", true, "max", "Set the maximum number of inactive TCP connections to a backend cached by each worker TCP thread"},
729
    {"setMaxTCPClientThreads", true, "n", "set the maximum of TCP client threads, handling TCP connections"},
730
    {"setMaxTCPConnectionDuration", true, "n", "set the maximum duration of an incoming TCP connection, in seconds. 0 means unlimited"},
731
    {"setMaxTCPConnectionsPerClient", true, "n", "set the maximum number of TCP connections per client. 0 means unlimited"},
732
    {"setMaxTCPQueriesPerConnection", true, "n", "set the maximum number of queries in an incoming TCP connection. 0 means unlimited"},
733
    {"setMaxTCPQueuedConnections", true, "n", "set the maximum number of TCP connections queued (waiting to be picked up by a client thread)"},
734
    {"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 65535"},
735
    {"setMetric", true, "name, value", "Set the value of a custom metric to the supplied value"},
736
    {"setPayloadSizeOnSelfGeneratedAnswers", true, "payloadSize", "set the UDP payload size advertised via EDNS on self-generated responses"},
737
    {"setPoolServerPolicy", true, "policy, pool", "set the server selection policy for this pool to that policy"},
738
    {"setPoolServerPolicyLua", true, "name, function, pool", "set the server selection policy for this pool to one named 'name' and provided by 'function'"},
739
    {"setPoolServerPolicyLuaFFI", true, "name, function, pool", "set the server selection policy for this pool to one named 'name' and provided by 'function'"},
740
    {"setPoolServerPolicyLuaFFIPerThread", true, "name, code", "set server selection policy for this pool to one named 'name' and returned by the Lua FFI code passed in 'code'"},
741
    {"setProxyProtocolACL", true, "{netmask, netmask}", "Set the netmasks who are allowed to send Proxy Protocol headers in front of queries/connections"},
742
    {"setProxyProtocolApplyACLToProxiedClients", true, "apply", "Whether the general ACL should be applied to the source IP address gathered from a Proxy Protocol header, in addition to being first applied to the source address seen by dnsdist"},
743
    {"setProxyProtocolMaximumPayloadSize", true, "max", "Set the maximum size of a Proxy Protocol payload, in bytes"},
744
    {"setQueryCount", true, "bool", "set whether queries should be counted"},
745
    {"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"},
746
    {"SetReducedTTLResponseAction", true, "percentage", "Reduce the TTL of records in a response to a given percentage"},
747
    {"setRingBuffersLockRetries", true, "n", "set the number of attempts to get a non-blocking lock to a ringbuffer shard before blocking"},
748
    {"setRingBuffersOptions", true, "{ lockRetries=int, recordQueries=true, recordResponses=true }", "set ringbuffer options"},
749
    {"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`"},
750
    {"setRoundRobinFailOnNoServer", true, "value", "By default the roundrobin load-balancing policy will still try to select a backend even if all backends are currently down. Setting this to true will make the policy fail and return that no server is available instead"},
751
    {"setRules", true, "list of rules", "replace the current rules with the supplied list of pairs of DNS Rules and DNS Actions (see `newRuleAction()`)"},
752
    {"setSecurityPollInterval", true, "n", "set the security polling interval to `n` seconds"},
753
    {"setSecurityPollSuffix", true, "suffix", "set the security polling suffix to the specified value"},
754
    {"setServerPolicy", true, "policy", "set server selection policy to that policy"},
755
    {"setServerPolicyLua", true, "name, function", "set server selection policy to one named 'name' and provided by 'function'"},
756
    {"setServerPolicyLuaFFI", true, "name, function", "set server selection policy to one named 'name' and provided by the Lua FFI 'function'"},
757
    {"setServerPolicyLuaFFIPerThread", true, "name, code", "set server selection policy to one named 'name' and returned by the Lua FFI code passed in 'code'"},
758
    {"setServFailWhenNoServer", true, "bool", "if set, return a ServFail when no servers are available, instead of the default behaviour of dropping the query"},
759
    {"setStaleCacheEntriesTTL", true, "n", "allows using cache entries expired for at most n seconds when there is no backend available to answer for a query"},
760
    {"setStructuredLogging", true, "value [, options]", "set whether log messages should be in structured-logging-like format"},
761
    {"setSyslogFacility", true, "facility", "set the syslog logging facility to 'facility'. Defaults to LOG_DAEMON"},
762
    {"setTCPDownstreamCleanupInterval", true, "interval", "minimum interval in seconds between two cleanups of the idle TCP downstream connections"},
763
    {"setTCPFastOpenKey", true, "string", "TCP Fast Open Key"},
764
    {"setTCPDownstreamMaxIdleTime", true, "time", "Maximum time in seconds that a downstream TCP connection to a backend might stay idle"},
765
    {"setTCPInternalPipeBufferSize", true, "size", "Set the size in bytes of the internal buffer of the pipes used internally to distribute connections to TCP (and DoT) workers threads"},
766
    {"setTCPRecvTimeout", true, "n", "set the read timeout on TCP connections from the client, in seconds"},
767
    {"setTCPSendTimeout", true, "n", "set the write timeout on TCP connections from the client, in seconds"},
768
    {"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"},
769
    {"setUDPSocketBufferSizes", true, "recv, send", "Set the size of the receive (SO_RCVBUF) and send (SO_SNDBUF) buffers for incoming UDP sockets"},
770
    {"setUDPTimeout", true, "n", "set the maximum time dnsdist will wait for a response from a backend over UDP, in seconds"},
771
    {"setVerbose", true, "bool", "set whether log messages at the verbose level will be logged"},
772
    {"setVerboseHealthChecks", true, "bool", "set whether health check errors will be logged"},
773
    {"setVerboseLogDestination", true, "destination file", "Set a destination file to write the 'verbose' log messages to, instead of sending them to syslog and/or the standard output"},
774
    {"setWebserverConfig", true, "[{password=string, apiKey=string, customHeaders, statsRequireAuthentication}]", "Updates webserver configuration"},
775
    {"setWeightedBalancingFactor", true, "factor", "Set the balancing factor for bounded-load weighted policies (whashed, wrandom)"},
776
    {"setWHashedPertubation", true, "value", "Set the hash perturbation value to be used in the whashed policy instead of a random one, allowing to have consistent whashed results on different instance"},
777
    {"show", true, "string", "outputs `string`"},
778
    {"showACL", true, "", "show our ACL set"},
779
    {"showBinds", true, "", "show listening addresses (frontends)"},
780
    {"showCacheHitResponseRules", true, "[{showUUIDs=false, truncateRuleWidth=-1}]", "show all defined cache hit response rules, optionally with their UUIDs and optionally truncated to a given width"},
781
    {"showConsoleACL", true, "", "show our current console ACL set"},
782
    {"showDNSCryptBinds", true, "", "display the currently configured DNSCrypt binds"},
783
    {"showDOHFrontends", true, "", "list all the available DOH frontends"},
784
    {"showDOH3Frontends", true, "", "list all the available DOH3 frontends"},
785
    {"showDOHResponseCodes", true, "", "show the HTTP response code statistics for the DoH frontends"},
786
    {"showDOQFrontends", true, "", "list all the available DOQ frontends"},
787
    {"showDynBlocks", true, "", "show dynamic blocks in force"},
788
    {"showPools", true, "", "show the available pools"},
789
    {"showPoolServerPolicy", true, "pool", "show server selection policy for this pool"},
790
    {"showResponseLatency", true, "", "show a plot of the response time latency distribution"},
791
    {"showResponseRules", true, "[{showUUIDs=false, truncateRuleWidth=-1}]", "show all defined response rules, optionally with their UUIDs and optionally truncated to a given width"},
792
    {"showRules", true, "[{showUUIDs=false, truncateRuleWidth=-1}]", "show all defined rules, optionally with their UUIDs and optionally truncated to a given width"},
793
    {"showSecurityStatus", true, "", "Show the security status"},
794
    {"showSelfAnsweredResponseRules", true, "[{showUUIDs=false, truncateRuleWidth=-1}]", "show all defined self-answered response rules, optionally with their UUIDs and optionally truncated to a given width"},
795
    {"showServerPolicy", true, "", "show name of currently operational server selection policy"},
796
    {"showServers", true, "[{showUUIDs=false}]", "output all servers, optionally with their UUIDs"},
797
    {"showTCPStats", true, "", "show some statistics regarding TCP"},
798
    {"showTLSErrorCounters", true, "", "show metrics about TLS handshake failures"},
799
    {"showTLSFrontends", true, "", "list all the available TLS contexts"},
800
    {"showVersion", true, "", "show the current version"},
801
    {"showWebserverConfig", true, "", "Show the current webserver configuration"},
802
    {"shutdown", true, "", "shut down `dnsdist`"},
803
    {"snmpAgent", true, "enableTraps [, daemonSocket]", "enable `SNMP` support. `enableTraps` is a boolean indicating whether traps should be sent and `daemonSocket` an optional string specifying how to connect to the daemon agent"},
804
    {"SetAdditionalProxyProtocolValueAction", true, "type, value", "Add a Proxy Protocol TLV value of this type"},
805
    {"SetDisableECSAction", true, "", "Disable the sending of ECS to the backend. Subsequent rules are processed after this action."},
806
    {"SetDisableValidationAction", true, "", "set the CD bit in the question, let it go through"},
807
    {"SetECSAction", true, "v4[, v6]", "Set the ECS prefix and prefix length sent to backends to an arbitrary value"},
808
    {"SetECSOverrideAction", true, "override", "Whether an existing EDNS Client Subnet value should be overridden (true) or not (false). Subsequent rules are processed after this action"},
809
    {"SetECSPrefixLengthAction", true, "v4, v6", "Set the ECS prefix length. Subsequent rules are processed after this action"},
810
    {"SetMacAddrAction", true, "option", "Add the source MAC address to the query as EDNS0 option option. This action is currently only supported on Linux. Subsequent rules are processed after this action"},
811
    {"SetEDNSOptionAction", true, "option, data", "Add arbitrary EDNS option and data to the query. Subsequent rules are processed after this action"},
812
    {"SetExtendedDNSErrorAction", true, "infoCode [, extraText]", "Set an Extended DNS Error status that will be added to the response corresponding to the current query. Subsequent rules are processed after this action"},
813
    {"SetExtendedDNSErrorResponseAction", true, "infoCode [, extraText]", "Set an Extended DNS Error status that will be added to this response. Subsequent rules are processed after this action"},
814
    {"SetNoRecurseAction", true, "", "strip RD bit from the question, let it go through"},
815
    {"setOutgoingDoHWorkerThreads", true, "n", "Number of outgoing DoH worker threads"},
816
    {"SetProxyProtocolValuesAction", true, "values", "Set the Proxy-Protocol values for this queries to 'values'"},
817
    {"SetSkipCacheAction", true, "", "Don’t lookup the cache for this query, don’t store the answer"},
818
    {"SetSkipCacheResponseAction", true, "", "Don’t store this response into the cache"},
819
    {"SetTagAction", true, "name, value", "set the tag named 'name' to the given value"},
820
    {"SetTagResponseAction", true, "name, value", "set the tag named 'name' to the given value"},
821
    {"SetTempFailureCacheTTLAction", true, "ttl", "set packetcache TTL for temporary failure replies"},
822
    {"SNIRule", true, "name", "Create a rule which matches on the incoming TLS SNI value, if any (DoT or DoH)"},
823
    {"SNMPTrapAction", true, "[reason]", "send an SNMP trap, adding the optional `reason` string as the query description"},
824
    {"SNMPTrapResponseAction", true, "[reason]", "send an SNMP trap, adding the optional `reason` string as the response description"},
825
    {"SpoofAction", true, "ip|list of ips [, options]", "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"},
826
    {"SpoofCNAMEAction", true, "cname [, options]", "Forge a response with the specified CNAME value"},
827
    {"SpoofRawAction", true, "raw|list of raws [, options]", "Forge a response with the specified record data as raw bytes. If you specify multiple raws (it is assumed they match the query type), all will get spoofed in"},
828
    {"SpoofSVCAction", true, "list of svcParams [, options]", "Forge a response with the specified SVC record data"},
829
    {"SuffixMatchNodeRule", true, "smn[, quiet]", "Matches based on a group of domain suffixes for rapid testing of membership. Pass true as second parameter to prevent listing of all domains matched"},
830
    {"TagRule", true, "name [, value]", "matches if the tag named 'name' is present, with the given 'value' matching if any"},
831
    {"TCAction", true, "", "create answer to query with TC and RD bits set, to move to TCP"},
832
    {"TCPRule", true, "[tcp]", "Matches question received over TCP if tcp is true, over UDP otherwise"},
833
    {"TCResponseAction", true, "", "truncate a response"},
834
    {"TeeAction", true, "remote [, addECS [, local]]", "send copy of query to remote, optionally adding ECS info, optionally set local address"},
835
    {"testCrypto", true, "", "test of the crypto all works"},
836
    {"TimedIPSetRule", true, "", "Create a rule which matches a set of IP addresses which expire"},
837
    {"topBandwidth", true, "top", "show top-`top` clients that consume the most bandwidth over length of ringbuffer"},
838
    {"topCacheHitResponseRules", true, "[top][, vars]", "show `top` cache-hit response rules"},
839
    {"topCacheInsertedResponseRules", true, "[top][, vars]", "show `top` cache-inserted response rules"},
840
    {"topClients", true, "n", "show top-`n` clients sending the most queries over length of ringbuffer"},
841
    {"topQueries", true, "n[, labels]", "show top 'n' queries, as grouped when optionally cut down to 'labels' labels"},
842
    {"topResponses", true, "n, kind[, labels]", "show top 'n' responses with RCODE=kind (0=NO Error, 2=ServFail, 3=NXDomain), as grouped when optionally cut down to 'labels' labels"},
843
    {"topResponseRules", true, "[top][, vars]", "show `top` response rules"},
844
    {"topRules", true, "[top][, vars]", "show `top` rules"},
845
    {"topSelfAnsweredResponseRules", true, "[top][, vars]", "show `top` self-answered response rules"},
846
    {"topSlow", true, "[top][, limit][, labels]", "show `top` queries slower than `limit` milliseconds (timeouts excepted), grouped by last `labels` labels"},
847
    {"topTimeouts", true, "[top][, labels]", "show `top` queries that timed out, grouped by last `labels` labels"},
848
    {"TrailingDataRule", true, "", "Matches if the query has trailing data"},
849
    {"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."},
850
    {"unregisterDynBPFFilter", true, "DynBPFFilter", "unregister this dynamic BPF filter"},
851
    {"webserver", true, "address:port", "launch a webserver with stats on that address"},
852
    {"whashed", false, "", "Weighted hashed ('sticky') distribution over available servers, based on the server 'weight' parameter"},
853
    {"chashed", false, "", "Consistent hashed ('sticky') distribution over available servers, also based on the server 'weight' parameter"},
854
    {"wrandom", false, "", "Weighted random over available servers, based on the server 'weight' parameter"},
855
};
856

857
#if defined(HAVE_LIBEDIT)
858
extern "C"
859
{
860
  static char* dnsdist_completion_generator(const char* text, int state)
861
  {
×
862
    string textStr(text);
×
863
    /* to keep it readable, we try to keep only 4 keywords per line
864
       and to start a new line when the first letter changes */
865
    static int s_counter = 0;
×
866
    int counter = 0;
×
867
    if (state == 0) {
×
868
      s_counter = 0;
×
869
    }
×
870

871
    for (const auto& keyword : s_consoleKeywords) {
×
872
      if (boost::starts_with(keyword.name, textStr) && counter++ == s_counter) {
×
873
        std::string value(keyword.name);
×
874
        s_counter++;
×
875
        if (keyword.function) {
×
876
          value += "(";
×
877
          if (keyword.parameters.empty()) {
×
878
            value += ")";
×
879
          }
×
880
        }
×
881
        return strdup(value.c_str());
×
882
      }
×
883
    }
×
884
    return nullptr;
×
885
  }
×
886

887
  static char** dnsdist_completion_callback(const char* text, int start, int end)
888
  {
×
NEW
889
    (void)end;
×
890
    char** matches = nullptr;
×
891
    if (start == 0) {
×
892
      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast): readline
893
      matches = rl_completion_matches(const_cast<char*>(text), &dnsdist_completion_generator);
×
894
    }
×
895

896
    // skip default filename completion.
897
    rl_attempted_completion_over = 1;
×
898

899
    return matches;
×
900
  }
×
901
}
902
#endif /* HAVE_LIBEDIT */
903
#endif /* DISABLE_COMPLETION */
904

905
namespace dnsdist::console
906
{
907
#ifndef DISABLE_COMPLETION
908
const std::vector<ConsoleKeyword>& getConsoleKeywords()
909
{
×
910
  return s_consoleKeywords;
×
911
}
×
912
#endif /* DISABLE_COMPLETION */
913

914
void setupCompletion()
915
{
673✔
916
#ifndef DISABLE_COMPLETION
673✔
917
#ifdef HAVE_LIBEDIT
673✔
918
  rl_attempted_completion_function = dnsdist_completion_callback;
673✔
919
  rl_completion_append_character = 0;
673✔
920
#endif /* DISABLE_COMPLETION */
673✔
921
#endif /* HAVE_LIBEDIT */
673✔
922
}
673✔
923

924
void clearHistory()
925
{
×
926
#ifdef HAVE_LIBEDIT
×
927
  clear_history();
×
928
#endif /* HAVE_LIBEDIT */
×
929
  s_confDelta.lock()->clear();
×
930
}
×
931

932
static void controlClientThread(ConsoleConnection&& conn)
933
{
275✔
934
  try {
275✔
935
    setThreadName("dnsdist/conscli");
275✔
936

937
    setTCPNoDelay(conn.getFD());
275✔
938

939
    //coverity[auto_causes_copy]
940
    const auto consoleKey = dnsdist::configuration::getCurrentRuntimeConfiguration().d_consoleKey;
275✔
941
    dnsdist::crypto::authenticated::Nonce theirs;
275✔
942
    dnsdist::crypto::authenticated::Nonce ours;
275✔
943
    dnsdist::crypto::authenticated::Nonce readingNonce;
275✔
944
    dnsdist::crypto::authenticated::Nonce writingNonce;
275✔
945
    ours.init();
275✔
946
    readn2(conn.getFD(), theirs.value.data(), theirs.value.size());
275✔
947
    writen2(conn.getFD(), ours.value.data(), ours.value.size());
275✔
948
    readingNonce.merge(ours, theirs);
275✔
949
    writingNonce.merge(theirs, ours);
275✔
950

951
    for (;;) {
546✔
952
      uint32_t len{0};
546✔
953
      if (getMsgLen32(conn.getFD(), &len) != ConsoleCommandResult::Valid) {
546✔
954
        break;
273✔
955
      }
273✔
956

957
      if (len == 0) {
273!
958
        /* just ACK an empty message
959
           with an empty response */
960
        putMsgLen32(conn.getFD(), 0);
×
961
        continue;
×
962
      }
×
963

964
      std::string line;
273✔
965
      //coverity[tainted_data]
966
      line.resize(len);
273✔
967
      readn2(conn.getFD(), line.data(), len);
273✔
968

969
      line = dnsdist::crypto::authenticated::decryptSym(line, consoleKey, readingNonce);
273✔
970

971
      string response;
273✔
972
      try {
273✔
973
        bool withReturn = true;
273✔
974
      retry:;
302✔
975
        try {
302✔
976
          auto lua = g_lua.lock();
302✔
977

978
          g_outputBuffer.clear();
302✔
979
          resetLuaSideEffect();
302✔
980
          auto ret = lua->executeCode<
302✔
981
            boost::optional<
302✔
982
              boost::variant<
302✔
983
                string,
302✔
984
                shared_ptr<DownstreamState>,
302✔
985
                ClientState*,
302✔
986
                std::unordered_map<string, double>>>>(withReturn ? ("return " + line) : line);
302✔
987

988
          if (ret) {
302✔
989
            if (const auto* dsValue = boost::get<shared_ptr<DownstreamState>>(&*ret)) {
58!
990
              if (*dsValue) {
×
991
                response = (*dsValue)->getName() + "\n";
×
992
              }
×
993
              else {
×
994
                response = "";
×
995
              }
×
996
            }
×
997
            else if (const auto* csValue = boost::get<ClientState*>(&*ret)) {
58!
998
              if (*csValue != nullptr) {
×
999
                response = (*csValue)->local.toStringWithPort() + "\n";
×
1000
              }
×
1001
              else {
×
1002
                response = "";
×
1003
              }
×
1004
            }
×
1005
            else if (const auto* strValue = boost::get<string>(&*ret)) {
58!
1006
              response = *strValue + "\n";
58✔
1007
            }
58✔
1008
            else if (const auto* mapValue = boost::get<std::unordered_map<string, double>>(&*ret)) {
×
1009
              using namespace json11;
×
1010
              Json::object obj;
×
1011
              for (const auto& value : *mapValue) {
×
1012
                obj[value.first] = value.second;
×
1013
              }
×
1014
              Json out = obj;
×
1015
              response = out.dump() + "\n";
×
1016
            }
×
1017
          }
58✔
1018
          else {
244✔
1019
            response = g_outputBuffer;
244✔
1020
          }
244✔
1021
          if (!getLuaNoSideEffect()) {
302✔
1022
            feedConfigDelta(line);
102✔
1023
          }
102✔
1024
        }
302✔
1025
        catch (const LuaContext::SyntaxErrorException&) {
302✔
1026
          if (withReturn) {
29!
1027
            withReturn = false;
29✔
1028
            // NOLINTNEXTLINE(cppcoreguidelines-avoid-goto)
1029
            goto retry;
29✔
1030
          }
29✔
1031
          throw;
×
1032
        }
29✔
1033
      }
302✔
1034
      catch (const LuaContext::WrongTypeException& e) {
273✔
1035
        response = "Command returned an object we can't print: " + std::string(e.what()) + "\n";
×
1036
        // tried to return something we don't understand
1037
      }
×
1038
      catch (const LuaContext::ExecutionErrorException& e) {
273✔
1039
        if (strcmp(e.what(), "invalid key to 'next'") == 0) {
2!
1040
          response = "Error: Parsing function parameters, did you forget parameter name?";
×
1041
        }
×
1042
        else {
2✔
1043
          response = "Error: " + string(e.what());
2✔
1044
        }
2✔
1045

1046
        try {
2✔
1047
          std::rethrow_if_nested(e);
2✔
1048
        }
2✔
1049
        catch (const std::exception& ne) {
2✔
1050
          // ne is the exception that was thrown from inside the lambda
1051
          response += ": " + string(ne.what());
×
1052
        }
×
1053
        catch (const PDNSException& ne) {
2✔
1054
          // ne is the exception that was thrown from inside the lambda
1055
          response += ": " + string(ne.reason);
×
1056
        }
×
1057
      }
2✔
1058
      catch (const LuaContext::SyntaxErrorException& e) {
273✔
1059
        response = "Error: " + string(e.what()) + ": ";
×
1060
      }
×
1061
      response = dnsdist::crypto::authenticated::encryptSym(response, consoleKey, writingNonce);
273✔
1062
      putMsgLen32(conn.getFD(), response.length());
273✔
1063
      writen2(conn.getFD(), response.c_str(), response.length());
273✔
1064
    }
273✔
1065
    if (dnsdist::configuration::getCurrentRuntimeConfiguration().d_logConsoleConnections) {
275✔
1066
      infolog("Closed control connection from %s", conn.getClient().toStringWithPort());
273✔
1067
    }
273✔
1068
  }
275✔
1069
  catch (const std::exception& e) {
275✔
1070
    infolog("Got an exception in client connection from %s: %s", conn.getClient().toStringWithPort(), e.what());
2✔
1071
  }
2✔
1072
}
275✔
1073

1074
void controlThread(Socket&& acceptFD)
1075
{
64✔
1076
  try {
64✔
1077
    setThreadName("dnsdist/control");
64✔
1078
    const ComboAddress local = dnsdist::configuration::getCurrentRuntimeConfiguration().d_consoleServerAddress;
64✔
1079
    s_connManager.setMaxConcurrentConnections(dnsdist::configuration::getImmutableConfiguration().d_consoleMaxConcurrentConnections);
64✔
1080

1081
    ComboAddress client;
64✔
1082
    // make sure that the family matches the one from the listening IP,
1083
    // so that getSocklen() returns the correct size later, otherwise
1084
    // the first IPv6 console connection might get refused
1085
    client.sin4.sin_family = local.sin4.sin_family;
64✔
1086

1087
    int sock{-1};
64✔
1088
    infolog("Accepting control connections on %s", local.toStringWithPort());
64✔
1089

1090
    while ((sock = SAccept(acceptFD.getHandle(), client)) >= 0) {
342✔
1091
      const auto& consoleKey = dnsdist::configuration::getCurrentRuntimeConfiguration().d_consoleKey;
278✔
1092
      FDWrapper socket(sock);
278✔
1093
      if (!dnsdist::crypto::authenticated::isValidKey(consoleKey)) {
278✔
1094
        vinfolog("Control connection from %s dropped because we don't have a valid key configured, please configure one using setKey()", client.toStringWithPort());
1!
1095
        continue;
1✔
1096
      }
1✔
1097

1098
      const auto& runtimeConfig = dnsdist::configuration::getCurrentRuntimeConfiguration();
277✔
1099
      if (!runtimeConfig.d_consoleACL.match(client)) {
277✔
1100
        vinfolog("Control connection from %s dropped because of ACL", client.toStringWithPort());
1!
1101
        continue;
1✔
1102
      }
1✔
1103

1104
      try {
276✔
1105
        ConsoleConnection conn(client, std::move(socket));
276✔
1106
        if (runtimeConfig.d_logConsoleConnections) {
276✔
1107
          warnlog("Got control connection from %s", client.toStringWithPort());
275✔
1108
        }
275✔
1109

1110
        std::thread clientThread(controlClientThread, std::move(conn));
276✔
1111
        clientThread.detach();
276✔
1112
      }
276✔
1113
      catch (const std::exception& e) {
276✔
1114
        infolog("Control connection died: %s", e.what());
1✔
1115
      }
1✔
1116
    }
276✔
1117
  }
64✔
1118
  catch (const std::exception& e) {
64✔
1119
    errlog("Control thread died: %s", e.what());
×
1120
  }
×
1121
}
64✔
1122
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc