• 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

40.31
/pdns/dnsdistdist/dnsdist-lua-inspection.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 <algorithm>
23
#include <fcntl.h>
24
#include <iterator>
25

26
#include "dnsdist.hh"
27
#include "dnsdist-console.hh"
28
#include "dnsdist-dynblocks.hh"
29
#include "dnsdist-frontend.hh"
30
#include "dnsdist-lua.hh"
31
#include "dnsdist-nghttp2.hh"
32
#include "dnsdist-rings.hh"
33
#include "dnsdist-tcp.hh"
34

35
#include "statnode.hh"
36

37
#ifndef DISABLE_TOP_N_BINDINGS
38
static LuaArray<std::vector<boost::variant<string, double>>> getGenResponses(uint64_t top, boost::optional<int> labels, const std::function<bool(const Rings::Response&)>& pred)
39
{
×
40
  setLuaNoSideEffect();
×
41
  map<DNSName, unsigned int> counts;
×
42
  unsigned int total = 0;
×
43
  {
×
44
    for (const auto& shard : g_rings.d_shards) {
×
45
      auto respRing = shard->respRing.lock();
×
46
      if (!labels) {
×
47
        for (const auto& entry : *respRing) {
×
48
          if (!pred(entry)) {
×
49
            continue;
×
50
          }
×
51
          counts[entry.name]++;
×
52
          total++;
×
53
        }
×
54
      }
×
55
      else {
×
56
        unsigned int lab = *labels;
×
57
        for (const auto& entry : *respRing) {
×
58
          if (!pred(entry)) {
×
59
            continue;
×
60
          }
×
61

62
          DNSName temp(entry.name);
×
63
          temp.trimToLabels(lab);
×
64
          counts[temp]++;
×
65
          total++;
×
66
        }
×
67
      }
×
68
    }
×
69
  }
×
70
  //      cout<<"Looked at "<<total<<" responses, "<<counts.size()<<" different ones"<<endl;
71
  vector<pair<unsigned int, DNSName>> rcounts;
×
72
  rcounts.reserve(counts.size());
×
73
  for (const auto& val : counts) {
×
74
    rcounts.emplace_back(val.second, val.first.makeLowerCase());
×
75
  }
×
76

77
  sort(rcounts.begin(), rcounts.end(), [](const decltype(rcounts)::value_type& lhs, const decltype(rcounts)::value_type& rhs) {
×
78
    return rhs.first < lhs.first;
×
79
  });
×
80

81
  LuaArray<vector<boost::variant<string, double>>> ret;
×
82
  ret.reserve(std::min(rcounts.size(), static_cast<size_t>(top + 1U)));
×
83
  int count = 1;
×
84
  unsigned int rest = 0;
×
85
  for (const auto& rcEntry : rcounts) {
×
86
    if (count == static_cast<int>(top + 1)) {
×
87
      rest += rcEntry.first;
×
88
    }
×
89
    else {
×
90
      ret.emplace_back(count++, std::vector<boost::variant<string, double>>{rcEntry.second.toString(), rcEntry.first, 100.0 * rcEntry.first / total});
×
91
    }
×
92
  }
×
93

94
  if (total > 0) {
×
95
    ret.push_back({count, {"Rest", rest, 100.0 * rest / total}});
×
96
  }
×
97
  else {
×
98
    ret.push_back({count, {"Rest", rest, 100.0}});
×
99
  }
×
100

101
  return ret;
×
102
}
×
103
#endif /* DISABLE_TOP_N_BINDINGS */
104

105
#ifndef DISABLE_DYNBLOCKS
106
#ifndef DISABLE_DEPRECATED_DYNBLOCK
107

108
using counts_t = std::unordered_map<ComboAddress, unsigned int, ComboAddress::addressOnlyHash, ComboAddress::addressOnlyEqual>;
109

110
static counts_t filterScore(const counts_t& counts,
111
                            double delta, unsigned int rate)
112
{
133✔
113
  counts_t ret;
133✔
114

115
  double lim = delta * rate;
133✔
116
  for (const auto& entry : counts) {
133✔
117
    if (entry.second > lim) {
54✔
118
      ret[entry.first] = entry.second;
31✔
119
    }
31✔
120
  }
54✔
121

122
  return ret;
133✔
123
}
133✔
124

125
using statvisitor_t = std::function<void(const StatNode&, const StatNode::Stat&, const StatNode::Stat&)>;
126

127
static void statNodeRespRing(statvisitor_t visitor, uint64_t seconds)
128
{
5✔
129
  timespec now{};
5✔
130
  gettime(&now);
5✔
131
  timespec cutoff{now};
5✔
132
  cutoff.tv_sec -= static_cast<time_t>(seconds);
5✔
133

134
  StatNode root;
5✔
135
  for (const auto& shard : g_rings.d_shards) {
50✔
136
    auto respRing = shard->respRing.lock();
50✔
137

138
    for (const auto& entry : *respRing) {
50✔
139
      if (now < entry.when) {
5!
140
        continue;
×
141
      }
×
142

143
      if (seconds != 0 && entry.when < cutoff) {
5✔
144
        continue;
1✔
145
      }
1✔
146

147
      const bool hit = entry.isACacheHit();
4✔
148
      root.submit(entry.name, ((entry.dh.rcode == 0 && entry.usec == std::numeric_limits<unsigned int>::max()) ? -1 : entry.dh.rcode), entry.size, hit, std::nullopt);
4!
149
    }
4✔
150
  }
50✔
151

152
  StatNode::Stat node;
5✔
153
  root.visit([visitor = std::move(visitor)](const StatNode* node_, const StatNode::Stat& self, const StatNode::Stat& children) { visitor(*node_, self, children); }, node);
25✔
154
}
5✔
155

156
static LuaArray<LuaAssociativeTable<std::string>> getRespRing(boost::optional<int> rcode)
157
{
×
158
  using entry_t = LuaAssociativeTable<std::string>;
×
159
  LuaArray<entry_t> ret;
×
160

161
  for (const auto& shard : g_rings.d_shards) {
×
162
    auto respRing = shard->respRing.lock();
×
163

164
    int count = 1;
×
165
    for (const auto& entry : *respRing) {
×
166
      if (rcode && (rcode.get() != entry.dh.rcode)) {
×
167
        continue;
×
168
      }
×
169
      entry_t newEntry;
×
170
      newEntry["qname"] = entry.name.toString();
×
171
      newEntry["rcode"] = std::to_string(entry.dh.rcode);
×
172
      ret.emplace_back(count, std::move(newEntry));
×
173
      count++;
×
174
    }
×
175
  }
×
176

177
  return ret;
×
178
}
×
179

180
static counts_t exceedRespGen(unsigned int rate, int seconds, const std::function<void(counts_t&, const Rings::Response&)>& visitor)
181
{
60✔
182
  counts_t counts;
60✔
183
  timespec now{};
60✔
184
  gettime(&now);
60✔
185
  timespec mintime{now};
60✔
186
  timespec cutoff{now};
60✔
187
  cutoff.tv_sec -= seconds;
60✔
188

189
  counts.reserve(g_rings.getNumberOfResponseEntries());
60✔
190

191
  for (const auto& shard : g_rings.d_shards) {
600✔
192
    auto respRing = shard->respRing.lock();
600✔
193
    for (const auto& entry : *respRing) {
2,071✔
194

195
      if (seconds != 0 && entry.when < cutoff) {
2,071!
196
        continue;
1,777✔
197
      }
1,777✔
198
      if (now < entry.when) {
294!
199
        continue;
×
200
      }
×
201

202
      visitor(counts, entry);
294✔
203
      if (entry.when < mintime) {
294✔
204
        mintime = entry.when;
34✔
205
      }
34✔
206
    }
294✔
207
  }
600✔
208

209
  double delta = seconds != 0 ? seconds : DiffTime(now, mintime);
60!
210
  return filterScore(counts, delta, rate);
60✔
211
}
60✔
212

213
static counts_t exceedQueryGen(unsigned int rate, int seconds, const std::function<void(counts_t&, const Rings::Query&)>& visitor)
214
{
73✔
215
  counts_t counts;
73✔
216
  timespec now{};
73✔
217
  gettime(&now);
73✔
218
  timespec mintime{now};
73✔
219
  timespec cutoff{now};
73✔
220
  cutoff.tv_sec -= seconds;
73✔
221

222
  counts.reserve(g_rings.getNumberOfQueryEntries());
73✔
223

224
  for (const auto& shard : g_rings.d_shards) {
730✔
225
    auto respRing = shard->queryRing.lock();
730✔
226
    for (const auto& entry : *respRing) {
2,327✔
227
      if (seconds != 0 && entry.when < cutoff) {
2,327!
228
        continue;
1,880✔
229
      }
1,880✔
230
      if (now < entry.when) {
447!
231
        continue;
×
232
      }
×
233
      visitor(counts, entry);
447✔
234
      if (entry.when < mintime) {
447✔
235
        mintime = entry.when;
47✔
236
      }
47✔
237
    }
447✔
238
  }
730✔
239

240
  double delta = seconds != 0 ? seconds : DiffTime(now, mintime);
73!
241
  return filterScore(counts, delta, rate);
73✔
242
}
73✔
243

244
static counts_t exceedRCode(unsigned int rate, int seconds, int rcode)
245
{
43✔
246
  return exceedRespGen(rate, seconds, [rcode](counts_t& counts, const Rings::Response& resp) {
268✔
247
    if (resp.dh.rcode == rcode) {
268✔
248
      counts[resp.requestor]++;
178✔
249
    }
178✔
250
  });
268✔
251
}
43✔
252

253
static counts_t exceedRespByterate(unsigned int rate, int seconds)
254
{
17✔
255
  return exceedRespGen(rate, seconds, [](counts_t& counts, const Rings::Response& resp) {
26✔
256
    counts[resp.requestor] += resp.size;
26✔
257
  });
26✔
258
}
17✔
259

260
#endif /* DISABLE_DEPRECATED_DYNBLOCK */
261
#endif /* DISABLE_DYNBLOCKS */
262

263
// NOLINTNEXTLINE(bugprone-exception-escape)
264
struct GrepQParams
265
{
266
  std::optional<Netmask> netmask;
267
  std::optional<DNSName> name;
268
  std::optional<unsigned int> msec;
269
  pdns::UniqueFilePtr outputFile{nullptr};
270
};
271

272
static std::optional<GrepQParams> parseGrepQParams(const LuaTypeOrArrayOf<std::string>& inp, boost::optional<LuaAssociativeTable<std::string>>& options)
273
{
11✔
274
  GrepQParams result{};
11✔
275

276
  if (options) {
11!
277
    std::string outputFileName;
×
278
    if (getOptionalValue<std::string>(options, "outputFile", outputFileName) > 0) {
×
279
      int fileDesc = open(outputFileName.c_str(), O_CREAT | O_EXCL | O_WRONLY, 0600);
×
280
      if (fileDesc < 0) {
×
281
        g_outputBuffer = "Error opening dump file for writing: " + stringerror() + "\n";
×
282
        return std::nullopt;
×
283
      }
×
284
      result.outputFile = pdns::UniqueFilePtr(fdopen(fileDesc, "w"));
×
285
      if (result.outputFile == nullptr) {
×
286
        g_outputBuffer = "Error opening dump file for writing: " + stringerror() + "\n";
×
287
        close(fileDesc);
×
288
        return std::nullopt;
×
289
      }
×
290
    }
×
291
    checkAllParametersConsumed("grepq", options);
×
292
  }
×
293

294
  vector<string> filters;
11✔
295
  const auto* str = boost::get<string>(&inp);
11✔
296
  if (str != nullptr) {
11!
297
    filters.push_back(*str);
11✔
298
  }
11✔
299
  else {
×
300
    auto values = boost::get<LuaArray<std::string>>(inp);
×
301
    for (const auto& filter : values) {
×
302
      filters.push_back(filter.second);
×
303
    }
×
304
  }
×
305

306
  for (const auto& filter : filters) {
11✔
307
    try {
11✔
308
      result.netmask = Netmask(filter);
11✔
309
      continue;
11✔
310
    }
11✔
311
    catch (...) {
11✔
312
      /* that's OK, let's try something else */
313
    }
11✔
314

315
    if (boost::ends_with(filter, "ms")) {
11!
316
      /* skip the ms at the end */
317
      const auto msecStr = filter.substr(0, filter.size() - 2);
×
318
      try {
×
319
        result.msec = pdns::checked_stoi<unsigned int>(msecStr);
×
320
        continue;
×
321
      }
×
322
      catch (...) {
×
323
        /* that's OK, let's try to parse as a DNS name */
324
      }
×
325
    }
×
326

327
    try {
11✔
328
      result.name = DNSName(filter);
11✔
329
    }
11✔
330
    catch (...) {
11✔
331
      g_outputBuffer = "Could not parse '" + filter + "' as domain name or netmask";
×
332
      return std::nullopt;
×
333
    }
×
334
  }
11✔
335
  return result;
22✔
336
}
11✔
337

338
template <class C>
339
static bool ringEntryMatches(const GrepQParams& params, const C& entry)
340
{
110✔
341
  bool nmmatch = true;
110✔
342
  bool dnmatch = true;
110✔
343
  bool msecmatch = true;
110✔
344
  if (params.netmask) {
110!
345
    nmmatch = params.netmask->match(entry.requestor);
×
346
  }
×
347
  if (params.name) {
110!
348
    if (entry.name.empty()) {
110!
349
      dnmatch = false;
×
350
    }
×
351
    else {
110✔
352
      dnmatch = entry.name.isPartOf(*params.name);
110✔
353
    }
110✔
354
  }
110✔
355

356
  constexpr bool response = std::is_same_v<C, Rings::Response>;
110✔
357
  if constexpr (response) {
110✔
358
    if (params.msec) {
52!
359
      msecmatch = (entry.usec / 1000 > *params.msec);
×
360
    }
×
361
  }
52✔
362

363
  return nmmatch && dnmatch && msecmatch;
110!
364
}
110✔
365

366
#ifndef DISABLE_DYNBLOCKS
367
using DynamicActionOptionalParameters = boost::optional<LuaAssociativeTable<std::string>>;
368

369
static void parseDynamicActionOptionalParameters(const std::string& directive, DynBlockRulesGroup::DynBlockRule& rule, const boost::optional<DNSAction::Action>& action, const DynamicActionOptionalParameters& optionalParameters)
370
{
28✔
371
  if (action && *action == DNSAction::Action::SetTag) {
28✔
372
    if (!optionalParameters) {
2!
373
      throw std::runtime_error("SetTag action passed to " + directive + " without additional parameters");
×
374
    }
×
375
    const auto& paramNameIt = optionalParameters->find("tagName");
2✔
376
    if (paramNameIt == optionalParameters->end()) {
2!
377
      throw std::runtime_error("SetTag action passed to " + directive + " without a tag name");
×
378
    }
×
379
    rule.d_tagSettings = std::make_shared<DynBlock::TagSettings>();
2✔
380
    rule.d_tagSettings->d_name = paramNameIt->second;
2✔
381
    const auto& paramValueIt = optionalParameters->find("tagValue");
2✔
382
    if (paramValueIt != optionalParameters->end()) {
2!
383
      rule.d_tagSettings->d_value = paramValueIt->second;
2✔
384
    }
2✔
385
  }
2✔
386
}
28✔
387
#endif /* DISABLE_DYNBLOCKS */
388

389
// NOLINTNEXTLINE(readability-function-cognitive-complexity): this function declares Lua bindings, even with a good refactoring it will likely blow up the threshold
390
void setupLuaInspection(LuaContext& luaCtx)
391
{
673✔
392
#ifndef DISABLE_TOP_N_BINDINGS
673✔
393
  luaCtx.writeFunction("topClients", [](boost::optional<uint64_t> top_) {
673✔
394
    setLuaNoSideEffect();
×
395
    uint64_t top = top_ ? *top_ : 10U;
×
396
    map<ComboAddress, unsigned int, ComboAddress::addressOnlyLessThan> counts;
×
397
    unsigned int total = 0;
×
398
    {
×
399
      for (const auto& shard : g_rings.d_shards) {
×
400
        auto respRing = shard->queryRing.lock();
×
401
        for (const auto& entry : *respRing) {
×
402
          counts[entry.requestor]++;
×
403
          total++;
×
404
        }
×
405
      }
×
406
    }
×
407
    vector<pair<unsigned int, ComboAddress>> rcounts;
×
408
    rcounts.reserve(counts.size());
×
409
    for (const auto& entry : counts) {
×
410
      rcounts.emplace_back(entry.second, entry.first);
×
411
    }
×
412

413
    sort(rcounts.begin(), rcounts.end(), [](const decltype(rcounts)::value_type& lhs, const decltype(rcounts)::value_type& rhs) {
×
414
      return rhs.first < lhs.first;
×
415
    });
×
416
    unsigned int count = 1;
×
417
    unsigned int rest = 0;
×
418
    boost::format fmt("%4d  %-40s %4d %4.1f%%\n");
×
419
    for (const auto& entry : rcounts) {
×
420
      if (count == top + 1) {
×
421
        rest += entry.first;
×
422
      }
×
423
      else {
×
424
        g_outputBuffer += (fmt % (count++) % entry.second.toString() % entry.first % (100.0 * entry.first / total)).str();
×
425
      }
×
426
    }
×
427
    g_outputBuffer += (fmt % (count) % "Rest" % rest % (total > 0 ? 100.0 * rest / total : 100.0)).str();
×
428
  });
×
429

430
  luaCtx.writeFunction("getTopQueries", [](uint64_t top, boost::optional<int> labels) {
673✔
431
    setLuaNoSideEffect();
×
432
    map<DNSName, unsigned int> counts;
×
433
    unsigned int total = 0;
×
434
    if (!labels) {
×
435
      for (const auto& shard : g_rings.d_shards) {
×
436
        auto respRing = shard->queryRing.lock();
×
437
        for (const auto& entry : *respRing) {
×
438
          counts[entry.name]++;
×
439
          total++;
×
440
        }
×
441
      }
×
442
    }
×
443
    else {
×
444
      unsigned int lab = *labels;
×
445
      for (const auto& shard : g_rings.d_shards) {
×
446
        auto respRing = shard->queryRing.lock();
×
447
        for (const auto& entry : *respRing) {
×
448
          auto name = entry.name;
×
449
          name.trimToLabels(lab);
×
450
          counts[name]++;
×
451
          total++;
×
452
        }
×
453
      }
×
454
    }
×
455

456
    vector<pair<unsigned int, DNSName>> rcounts;
×
457
    rcounts.reserve(counts.size());
×
458
    for (const auto& entry : counts) {
×
459
      rcounts.emplace_back(entry.second, entry.first.makeLowerCase());
×
460
    }
×
461

462
    sort(rcounts.begin(), rcounts.end(), [](const decltype(rcounts)::value_type& lhs, const decltype(rcounts)::value_type& rhs) {
×
463
      return rhs.first < lhs.first;
×
464
    });
×
465

466
    std::unordered_map<unsigned int, vector<boost::variant<string, double>>> ret;
×
467
    unsigned int count = 1;
×
468
    unsigned int rest = 0;
×
469
    for (const auto& entry : rcounts) {
×
470
      if (count == top + 1) {
×
471
        rest += entry.first;
×
472
      }
×
473
      else {
×
474
        ret.insert({count++, {entry.second.toString(), entry.first, 100.0 * entry.first / total}});
×
475
      }
×
476
    }
×
477

478
    if (total > 0) {
×
479
      ret.insert({count, {"Rest", rest, 100.0 * rest / total}});
×
480
    }
×
481
    else {
×
482
      ret.insert({count, {"Rest", rest, 100.0}});
×
483
    }
×
484

485
    return ret;
×
486
  });
×
487

488
  luaCtx.executeCode(R"(function topQueries(top, labels) top = top or 10; for k,v in ipairs(getTopQueries(top,labels)) do show(string.format("%4d  %-40s %4d %4.1f%%",k,v[1],v[2], v[3])) end end)");
673✔
489

490
  luaCtx.writeFunction("getResponseRing", []() {
673✔
491
    setLuaNoSideEffect();
×
492
    size_t totalEntries = 0;
×
493
    std::vector<boost::circular_buffer<Rings::Response>> rings;
×
494
    rings.reserve(g_rings.getNumberOfShards());
×
495
    for (const auto& shard : g_rings.d_shards) {
×
496
      {
×
497
        auto respRing = shard->respRing.lock();
×
498
        rings.push_back(*respRing);
×
499
      }
×
500
      totalEntries += rings.back().size();
×
501
    }
×
502
    vector<std::unordered_map<string, boost::variant<unsigned int, string>>> ret;
×
503
    ret.reserve(totalEntries);
×
504
    for (const auto& ring : rings) {
×
505
      for (const auto& entry : ring) {
×
506
        decltype(ret)::value_type item;
×
507
        item["name"] = entry.name.toString();
×
508
        item["qtype"] = entry.qtype;
×
509
        item["rcode"] = entry.dh.rcode;
×
510
        item["usec"] = entry.usec;
×
511
        ret.push_back(std::move(item));
×
512
      }
×
513
    }
×
514
    return ret;
×
515
  });
×
516

517
  luaCtx.writeFunction("getTopResponses", [](uint64_t top, uint64_t kind, boost::optional<int> labels) {
673✔
518
    return getGenResponses(top, labels, [kind](const Rings::Response& resp) { return resp.dh.rcode == kind; });
×
519
  });
×
520

521
  luaCtx.executeCode(R"(function topResponses(top, kind, labels) top = top or 10; kind = kind or 0; for k,v in ipairs(getTopResponses(top, kind, labels)) do show(string.format("%4d  %-40s %4d %4.1f%%",k,v[1],v[2],v[3])) end end)");
673✔
522

523
  luaCtx.writeFunction("getSlowResponses", [](uint64_t top, uint64_t msec, boost::optional<int> labels, boost::optional<bool> timeouts) {
673✔
524
    return getGenResponses(top, labels, [msec, timeouts](const Rings::Response& resp) {
×
525
      if (timeouts && *timeouts) {
×
526
        return resp.usec == std::numeric_limits<unsigned int>::max();
×
527
      }
×
528
      return resp.usec > msec * 1000 && resp.usec != std::numeric_limits<unsigned int>::max();
×
529
    });
×
530
  });
×
531

532
  luaCtx.executeCode(R"(function topSlow(top, msec, labels) top = top or 10; msec = msec or 500; for k,v in ipairs(getSlowResponses(top, msec, labels, false)) do show(string.format("%4d  %-40s %4d %4.1f%%",k,v[1],v[2],v[3])) end end)");
673✔
533

534
  luaCtx.executeCode(R"(function topTimeouts(top, labels) top = top or 10; for k,v in ipairs(getSlowResponses(top, 0, labels, true)) do show(string.format("%4d  %-40s %4d %4.1f%%",k,v[1],v[2],v[3])) end end)");
673✔
535

536
  luaCtx.writeFunction("getTopBandwidth", [](uint64_t top) {
673✔
537
    setLuaNoSideEffect();
×
538
    return g_rings.getTopBandwidth(top);
×
539
  });
×
540

541
  luaCtx.executeCode(R"(function topBandwidth(top) top = top or 10; for k,v in ipairs(getTopBandwidth(top)) do show(string.format("%4d  %-40s %4d %4.1f%%",k,v[1],v[2],v[3])) end end)");
673✔
542
#endif /* DISABLE_TOP_N_BINDINGS */
673✔
543

544
  luaCtx.writeFunction("delta", []() {
673✔
545
    setLuaNoSideEffect();
×
546
    // we hold the lua lock already!
547
    for (const auto& entry : dnsdist::console::getConfigurationDelta()) {
×
548
      tm entryTime{};
×
549
      localtime_r(&entry.first.tv_sec, &entryTime);
×
550
      std::array<char, 80> date{};
×
551
      strftime(date.data(), date.size() - 1, "-- %a %b %d %Y %H:%M:%S %Z\n", &entryTime);
×
552
      g_outputBuffer += date.data();
×
553
      g_outputBuffer += entry.second + "\n";
×
554
    }
×
555
  });
×
556

557
  luaCtx.writeFunction("grepq", [](const LuaTypeOrArrayOf<std::string>& inp, boost::optional<unsigned int> limit, boost::optional<LuaAssociativeTable<std::string>> options) {
673✔
558
    setLuaNoSideEffect();
11✔
559

560
    auto paramsOrError = parseGrepQParams(inp, options);
11✔
561
    if (!paramsOrError) {
11!
562
      return;
×
563
    }
×
564
    auto params = std::move(*paramsOrError);
11✔
565

566
    std::vector<Rings::Query> queries;
11✔
567
    std::vector<Rings::Response> responses;
11✔
568
    queries.reserve(g_rings.getNumberOfQueryEntries());
11✔
569
    responses.reserve(g_rings.getNumberOfResponseEntries());
11✔
570
    for (const auto& shard : g_rings.d_shards) {
110✔
571
      {
110✔
572
        auto respRing = shard->queryRing.lock();
110✔
573
        for (const auto& entry : *respRing) {
110✔
574
          queries.push_back(entry);
58✔
575
        }
58✔
576
      }
110✔
577
      {
110✔
578
        auto respRing = shard->respRing.lock();
110✔
579
        for (const auto& entry : *respRing) {
110✔
580
          responses.push_back(entry);
52✔
581
        }
52✔
582
      }
110✔
583
    }
110✔
584

585
    sort(queries.begin(), queries.end(), [](const decltype(queries)::value_type& lhs, const decltype(queries)::value_type& rhs) {
159✔
586
      return rhs.when < lhs.when;
159✔
587
    });
159✔
588

589
    sort(responses.begin(), responses.end(), [](const decltype(responses)::value_type& lhs, const decltype(responses)::value_type& rhs) {
153✔
590
      return rhs.when < lhs.when;
153✔
591
    });
153✔
592

593
    unsigned int num = 0;
11✔
594
    timespec now{};
11✔
595
    gettime(&now);
11✔
596

597
    std::multimap<struct timespec, string> out;
11✔
598

599
    boost::format fmt("%-7.1f %-47s %-12s %-12s %-5d %-25s %-5s %-6.1f %-2s %-2s %-2s %-s\n");
11✔
600
    const auto headLine = (fmt % "Time" % "Client" % "Protocol" % "Server" % "ID" % "Name" % "Type" % "Lat." % "TC" % "RD" % "AA" % "Rcode").str();
11✔
601
    if (!params.outputFile) {
11!
602
      g_outputBuffer += headLine;
11✔
603
    }
11✔
604
    else {
×
605
      fprintf(params.outputFile.get(), "%s", headLine.c_str());
×
606
    }
×
607

608
    if (!params.msec) {
11!
609
      for (const auto& entry : queries) {
58✔
610
        if (!ringEntryMatches(params, entry)) {
58!
611
          continue;
×
612
        }
×
613
        QType qtype(entry.qtype);
58✔
614
        std::string extra;
58✔
615
        if (entry.dh.opcode != 0) {
58!
616
          extra = " (" + Opcode::to_s(entry.dh.opcode) + ")";
×
617
        }
×
618
        out.emplace(entry.when, (fmt % DiffTime(now, entry.when) % entry.requestor.toStringWithPort() % dnsdist::Protocol(entry.protocol).toString() % "" % htons(entry.dh.id) % entry.name.toString() % qtype.toString() % "" % (entry.dh.tc != 0 ? "TC" : "") % (entry.dh.rd != 0 ? "RD" : "") % (entry.dh.aa != 0 ? "AA" : "") % ("Question" + extra)).str());
58!
619

620
        if (limit && *limit == ++num) {
58!
621
          break;
×
622
        }
×
623
      }
58✔
624
    }
11✔
625
    num = 0;
11✔
626

627
    string extra;
11✔
628
    for (const auto& entry : responses) {
52✔
629
      if (!ringEntryMatches(params, entry)) {
52!
630
        continue;
×
631
      }
×
632
      QType qtype(entry.qtype);
52✔
633
      if (entry.dh.rcode == 0) {
52!
634
        extra = ". " + std::to_string(htons(entry.dh.ancount)) + " answers";
52✔
635
      }
52✔
636
      else {
×
637
        extra.clear();
×
638
      }
×
639

640
      std::string server = entry.ds.toStringWithPort();
52✔
641
      std::string protocol = dnsdist::Protocol(entry.protocol).toString();
52✔
642
      if (server == "0.0.0.0:0") {
52!
643
        server = "Cache";
×
644
        protocol = "-";
×
645
      }
×
646
      if (entry.usec != std::numeric_limits<decltype(entry.usec)>::max()) {
52✔
647
        out.emplace(entry.when, (fmt % DiffTime(now, entry.when) % entry.requestor.toStringWithPort() % protocol % server % htons(entry.dh.id) % entry.name.toString() % qtype.toString() % (entry.usec / 1000.0) % (entry.dh.tc != 0 ? "TC" : "") % (entry.dh.rd != 0 ? "RD" : "") % (entry.dh.aa != 0 ? "AA" : "") % (RCode::to_s(entry.dh.rcode) + extra)).str());
48!
648
      }
48✔
649
      else {
4✔
650
        out.emplace(entry.when, (fmt % DiffTime(now, entry.when) % entry.requestor.toStringWithPort() % protocol % server % htons(entry.dh.id) % entry.name.toString() % qtype.toString() % "T.O" % (entry.dh.tc != 0 ? "TC" : "") % (entry.dh.rd != 0 ? "RD" : "") % (entry.dh.aa != 0 ? "AA" : "") % (RCode::to_s(entry.dh.rcode) + extra)).str());
4!
651
      }
4✔
652

653
      if (limit && *limit == ++num) {
52!
654
        break;
×
655
      }
×
656
    }
52✔
657

658
    for (const auto& entry : out) {
110✔
659
      if (!params.outputFile) {
110!
660
        g_outputBuffer += entry.second;
110✔
661
      }
110✔
662
      else {
×
663
        fprintf(params.outputFile.get(), "%s", entry.second.c_str());
×
664
      }
×
665
    }
110✔
666
  });
11✔
667

668
  luaCtx.writeFunction("showResponseLatency", []() {
673✔
669
    setLuaNoSideEffect();
×
670
    map<double, unsigned int> histo;
×
671
    double bin = 100;
×
672
    for (int idx = 0; idx < 15; ++idx) {
×
673
      histo[bin];
×
674
      bin *= 2;
×
675
    }
×
676

677
    double totlat = 0;
×
678
    unsigned int size = 0;
×
679
    {
×
680
      for (const auto& shard : g_rings.d_shards) {
×
681
        auto respRing = shard->respRing.lock();
×
682
        for (const auto& entry : *respRing) {
×
683
          /* skip actively discovered timeouts */
684
          if (entry.usec == std::numeric_limits<unsigned int>::max()) {
×
685
            continue;
×
686
          }
×
687

688
          ++size;
×
689
          auto iter = histo.lower_bound(entry.usec);
×
690
          if (iter != histo.end()) {
×
691
            iter->second++;
×
692
          }
×
693
          else {
×
694
            histo.rbegin()++;
×
695
          }
×
696
          totlat += entry.usec;
×
697
        }
×
698
      }
×
699
    }
×
700

701
    if (size == 0) {
×
702
      g_outputBuffer = "No traffic yet.\n";
×
703
      return;
×
704
    }
×
705

706
    g_outputBuffer = (boost::format("Average response latency: %.02f ms\n") % (0.001 * totlat / size)).str();
×
707
    double highest = 0;
×
708

709
    for (const auto& entry : histo) {
×
710
      highest = std::max(highest, entry.second * 1.0);
×
711
    }
×
712
    boost::format fmt("%7.2f\t%s\n");
×
713
    g_outputBuffer += (fmt % "ms" % "").str();
×
714

715
    for (const auto& entry : histo) {
×
716
      int stars = static_cast<int>(70.0 * entry.second / highest);
×
717
      char value = '*';
×
718
      if (stars == 0 && entry.second != 0) {
×
719
        stars = 1; // you get 1 . to show something is there..
×
720
        if (70.0 * entry.second / highest > 0.5) {
×
721
          value = ':';
×
722
        }
×
723
        else {
×
724
          value = '.';
×
725
        }
×
726
      }
×
727
      g_outputBuffer += (fmt % (entry.first / 1000.0) % string(stars, value)).str();
×
728
    }
×
729
  });
×
730

731
  luaCtx.writeFunction("showTCPStats", [] {
673✔
732
    setLuaNoSideEffect();
×
733
    const auto& immutableConfig = dnsdist::configuration::getImmutableConfiguration();
×
734
    ostringstream ret;
×
735
    boost::format fmt("%-12d %-12d %-12d %-12d");
×
736
    ret << (fmt % "Workers" % "Max Workers" % "Queued" % "Max Queued") << endl;
×
737
    ret << (fmt % g_tcpclientthreads->getThreadsCount() % immutableConfig.d_maxTCPClientThreads % g_tcpclientthreads->getQueuedCount() % immutableConfig.d_maxTCPQueuedConnections) << endl;
×
738
    ret << endl;
×
739

740
    ret << "Frontends:" << endl;
×
741
    fmt = boost::format("%-3d %-20.20s %-20d %-20d %-20d %-25d %-20d %-20d %-20d %-20f %-20f %-20d %-20d %-25d %-25d %-15d %-15d %-15d %-15d %-15d");
×
742
    ret << (fmt % "#" % "Address" % "Connections" % "Max concurrent conn" % "Died reading query" % "Died sending response" % "Gave up" % "Client timeouts" % "Downstream timeouts" % "Avg queries/conn" % "Avg duration" % "TLS new sessions" % "TLS Resumptions" % "TLS unknown ticket keys" % "TLS inactive ticket keys" % "TLS 1.0" % "TLS 1.1" % "TLS 1.2" % "TLS 1.3" % "TLS other") << endl;
×
743

744
    size_t counter = 0;
×
745
    for (const auto& frontend : dnsdist::getFrontends()) {
×
746
      ret << (fmt % counter % frontend->local.toStringWithPort() % frontend->tcpCurrentConnections % frontend->tcpMaxConcurrentConnections % frontend->tcpDiedReadingQuery % frontend->tcpDiedSendingResponse % frontend->tcpGaveUp % frontend->tcpClientTimeouts % frontend->tcpDownstreamTimeouts % frontend->tcpAvgQueriesPerConnection % frontend->tcpAvgConnectionDuration % frontend->tlsNewSessions % frontend->tlsResumptions % frontend->tlsUnknownTicketKey % frontend->tlsInactiveTicketKey % frontend->tls10queries % frontend->tls11queries % frontend->tls12queries % frontend->tls13queries % frontend->tlsUnknownqueries) << endl;
×
747
      ++counter;
×
748
    }
×
749
    ret << endl;
×
750

751
    ret << "Backends:" << endl;
×
752
    fmt = boost::format("%-3d %-20.20s %-20.20s %-20d %-20d %-25d %-25d %-20d %-20d %-20d %-20d %-20d %-20d %-20d %-20d %-20f %-20f");
×
753
    ret << (fmt % "#" % "Name" % "Address" % "Connections" % "Max concurrent conn" % "Died sending query" % "Died reading response" % "Gave up" % "Read timeouts" % "Write timeouts" % "Connect timeouts" % "Too many conn" % "Total connections" % "Reused connections" % "TLS resumptions" % "Avg queries/conn" % "Avg duration") << endl;
×
754

755
    counter = 0;
×
756
    for (const auto& backend : dnsdist::configuration::getCurrentRuntimeConfiguration().d_backends) {
×
757
      ret << (fmt % counter % backend->getName() % backend->d_config.remote.toStringWithPort() % backend->tcpCurrentConnections % backend->tcpMaxConcurrentConnections % backend->tcpDiedSendingQuery % backend->tcpDiedReadingResponse % backend->tcpGaveUp % backend->tcpReadTimeouts % backend->tcpWriteTimeouts % backend->tcpConnectTimeouts % backend->tcpTooManyConcurrentConnections % backend->tcpNewConnections % backend->tcpReusedConnections % backend->tlsResumptions % backend->tcpAvgQueriesPerConnection % backend->tcpAvgConnectionDuration) << endl;
×
758
      ++counter;
×
759
    }
×
760

761
    g_outputBuffer = ret.str();
×
762
  });
×
763

764
  luaCtx.writeFunction("showTLSErrorCounters", [] {
673✔
765
    setLuaNoSideEffect();
×
766
    ostringstream ret;
×
767
    boost::format fmt("%-3d %-20.20s %-23d %-23d %-23d %-23d %-23d %-23d %-23d %-23d");
×
768

769
    ret << (fmt % "#" % "Address" % "DH key too small" % "Inappropriate fallback" % "No shared cipher" % "Unknown cipher type" % "Unknown exchange type" % "Unknown protocol" % "Unsupported EC" % "Unsupported protocol") << endl;
×
770

771
    size_t counter = 0;
×
772
    for (const auto& frontend : dnsdist::getFrontends()) {
×
773
      if (!frontend->hasTLS()) {
×
774
        continue;
×
775
      }
×
776
      const TLSErrorCounters* errorCounters = nullptr;
×
777
      if (frontend->tlsFrontend != nullptr) {
×
778
        errorCounters = &frontend->tlsFrontend->d_tlsCounters;
×
779
      }
×
780
      else if (frontend->dohFrontend != nullptr) {
×
781
        errorCounters = &frontend->dohFrontend->d_tlsContext.d_tlsCounters;
×
782
      }
×
783
      if (errorCounters == nullptr) {
×
784
        continue;
×
785
      }
×
786

787
      ret << (fmt % counter % frontend->local.toStringWithPort() % errorCounters->d_dhKeyTooSmall % errorCounters->d_inappropriateFallBack % errorCounters->d_noSharedCipher % errorCounters->d_unknownCipherType % errorCounters->d_unknownKeyExchangeType % errorCounters->d_unknownProtocol % errorCounters->d_unsupportedEC % errorCounters->d_unsupportedProtocol) << endl;
×
788
      ++counter;
×
789
    }
×
790
    ret << endl;
×
791

792
    g_outputBuffer = ret.str();
×
793
  });
×
794

795
  luaCtx.writeFunction("requestTCPStatesDump", [] {
673✔
796
    setLuaNoSideEffect();
×
797
    extern std::atomic<uint64_t> g_tcpStatesDumpRequested;
×
798
    g_tcpStatesDumpRequested += g_tcpclientthreads->getThreadsCount();
×
799
  });
×
800

801
  luaCtx.writeFunction("requestDoHStatesDump", [] {
673✔
802
    setLuaNoSideEffect();
×
803
#if defined(HAVE_DNS_OVER_HTTPS) && defined(HAVE_NGHTTP2)
×
804
    g_dohStatesDumpRequested += g_dohClientThreads->getThreadsCount();
×
805
#endif
×
806
  });
×
807

808
  luaCtx.writeFunction("dumpStats", [] {
673✔
809
    setLuaNoSideEffect();
1✔
810
    vector<string> leftcolumn;
1✔
811
    vector<string> rightcolumn;
1✔
812

813
    boost::format fmt("%-35s\t%+11s");
1✔
814
    g_outputBuffer.clear();
1✔
815
    auto entries = *dnsdist::metrics::g_stats.entries.read_lock();
1✔
816

817
    // Filter entries to just the ones without label, for clearer output
818
    std::vector<std::reference_wrapper<decltype(entries)::value_type>> unlabeledEntries;
1✔
819
    std::copy_if(entries.begin(), entries.end(), std::back_inserter(unlabeledEntries), [](const decltype(entries)::value_type& triple) { return triple.d_labels.empty(); });
86✔
820

821
    sort(unlabeledEntries.begin(), unlabeledEntries.end(),
1✔
822
         [](const decltype(entries)::value_type& lhs, const decltype(entries)::value_type& rhs) {
641✔
823
           return lhs.d_name < rhs.d_name;
641✔
824
         });
641✔
825
    boost::format flt("    %9.1f");
1✔
826
    for (const auto& entryRef : unlabeledEntries) {
86✔
827
      const auto& entry = entryRef.get();
86✔
828
      string second;
86✔
829
      if (const auto& val = std::get_if<pdns::stat_t*>(&entry.d_value)) {
86✔
830
        second = std::to_string((*val)->load());
42✔
831
      }
42✔
832
      else if (const auto& adval = std::get_if<pdns::stat_double_t*>(&entry.d_value)) {
44✔
833
        second = (flt % (*adval)->load()).str();
24✔
834
      }
24✔
835
      else if (const auto& func = std::get_if<dnsdist::metrics::Stats::statfunction_t>(&entry.d_value)) {
20!
836
        second = std::to_string((*func)(entry.d_name));
20✔
837
      }
20✔
838

839
      if (leftcolumn.size() < unlabeledEntries.size() / 2) {
86✔
840
        leftcolumn.push_back((fmt % entry.d_name % second).str());
43✔
841
      }
43✔
842
      else {
43✔
843
        rightcolumn.push_back((fmt % entry.d_name % second).str());
43✔
844
      }
43✔
845
    }
86✔
846

847
    auto leftiter = leftcolumn.begin();
1✔
848
    auto rightiter = rightcolumn.begin();
1✔
849
    boost::format clmn("%|0t|%1% %|51t|%2%\n");
1✔
850

851
    for (; leftiter != leftcolumn.end() || rightiter != rightcolumn.end();) {
44!
852
      string lentry;
43✔
853
      string rentry;
43✔
854
      if (leftiter != leftcolumn.end()) {
43!
855
        lentry = *leftiter;
43✔
856
        leftiter++;
43✔
857
      }
43✔
858
      if (rightiter != rightcolumn.end()) {
43!
859
        rentry = *rightiter;
43✔
860
        rightiter++;
43✔
861
      }
43✔
862
      g_outputBuffer += (clmn % lentry % rentry).str();
43✔
863
    }
43✔
864
  });
1✔
865

866
#ifndef DISABLE_DYNBLOCKS
673✔
867
#ifndef DISABLE_DEPRECATED_DYNBLOCK
673✔
868
  luaCtx.writeFunction("exceedServFails", [](unsigned int rate, int seconds) {
673✔
869
    setLuaNoSideEffect();
43✔
870
    return exceedRCode(rate, seconds, RCode::ServFail);
43✔
871
  });
43✔
872
  luaCtx.writeFunction("exceedNXDOMAINs", [](unsigned int rate, int seconds) {
673✔
873
    setLuaNoSideEffect();
×
874
    return exceedRCode(rate, seconds, RCode::NXDomain);
×
875
  });
×
876

877
  luaCtx.writeFunction("exceedRespByterate", [](unsigned int rate, int seconds) {
673✔
878
    setLuaNoSideEffect();
17✔
879
    return exceedRespByterate(rate, seconds);
17✔
880
  });
17✔
881

882
  luaCtx.writeFunction("exceedQTypeRate", [](uint16_t type, unsigned int rate, int seconds) {
673✔
883
    setLuaNoSideEffect();
×
884
    return exceedQueryGen(rate, seconds, [type](counts_t& counts, const Rings::Query& query) {
×
885
      if (query.qtype == type) {
×
886
        counts[query.requestor]++;
×
887
      }
×
888
    });
×
889
  });
×
890

891
  luaCtx.writeFunction("exceedQRate", [](unsigned int rate, int seconds) {
673✔
892
    setLuaNoSideEffect();
73✔
893
    return exceedQueryGen(rate, seconds, [](counts_t& counts, const Rings::Query& query) {
447✔
894
      counts[query.requestor]++;
447✔
895
    });
447✔
896
  });
73✔
897

898
  luaCtx.writeFunction("getRespRing", getRespRing);
673✔
899

900
  /* StatNode */
901
  luaCtx.registerFunction<unsigned int (StatNode::*)() const>("numChildren",
673✔
902
                                                              [](const StatNode& node) -> unsigned int {
673✔
903
                                                                return node.children.size();
×
904
                                                              });
×
905
  luaCtx.registerMember("fullname", &StatNode::fullname);
673✔
906
  luaCtx.registerMember("labelsCount", &StatNode::labelsCount);
673✔
907
  luaCtx.registerMember("servfails", &StatNode::Stat::servfails);
673✔
908
  luaCtx.registerMember("nxdomains", &StatNode::Stat::nxdomains);
673✔
909
  luaCtx.registerMember("queries", &StatNode::Stat::queries);
673✔
910
  luaCtx.registerMember("noerrors", &StatNode::Stat::noerrors);
673✔
911
  luaCtx.registerMember("drops", &StatNode::Stat::drops);
673✔
912
  luaCtx.registerMember("bytes", &StatNode::Stat::bytes);
673✔
913
  luaCtx.registerMember("hits", &StatNode::Stat::hits);
673✔
914

915
  luaCtx.writeFunction("statNodeRespRing", [](statvisitor_t visitor, boost::optional<uint64_t> seconds) {
673✔
916
    statNodeRespRing(std::move(visitor), seconds ? *seconds : 0U);
5✔
917
  });
5✔
918
#endif /* DISABLE_DEPRECATED_DYNBLOCK */
673✔
919

920
  /* DynBlockRulesGroup */
921
  luaCtx.writeFunction("dynBlockRulesGroup", []() { return std::make_shared<DynBlockRulesGroup>(); });
673✔
922
  // NOLINTNEXTLINE(performance-unnecessary-value-param): optional parameters cannot be passed by const reference
923
  luaCtx.registerFunction<void (std::shared_ptr<DynBlockRulesGroup>::*)(unsigned int, unsigned int, const std::string&, unsigned int, boost::optional<DNSAction::Action>, boost::optional<unsigned int>, DynamicActionOptionalParameters)>("setQueryRate", [](std::shared_ptr<DynBlockRulesGroup>& group, unsigned int rate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, boost::optional<DNSAction::Action> action, boost::optional<unsigned int> warningRate, DynamicActionOptionalParameters optionalParameters) {
673✔
924
    if (group) {
18!
925
      DynBlockRulesGroup::DynBlockRule rule(reason, blockDuration, rate, warningRate ? *warningRate : 0, seconds, action ? *action : DNSAction::Action::None);
18✔
926
      parseDynamicActionOptionalParameters("setQueryRate", rule, action, optionalParameters);
18✔
927
      group->setQueryRate(std::move(rule));
18✔
928
    }
18✔
929
  });
18✔
930
  // NOLINTNEXTLINE(performance-unnecessary-value-param): optional parameters cannot be passed by const reference
931
  luaCtx.registerFunction<void (std::shared_ptr<DynBlockRulesGroup>::*)(unsigned int, unsigned int, const std::string&, unsigned int, boost::optional<DNSAction::Action>, boost::optional<unsigned int>, DynamicActionOptionalParameters)>("setResponseByteRate", [](std::shared_ptr<DynBlockRulesGroup>& group, unsigned int rate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, boost::optional<DNSAction::Action> action, boost::optional<unsigned int> warningRate, DynamicActionOptionalParameters optionalParameters) {
673✔
932
    if (group) {
2!
933
      DynBlockRulesGroup::DynBlockRule rule(reason, blockDuration, rate, warningRate ? *warningRate : 0, seconds, action ? *action : DNSAction::Action::None);
2!
934
      parseDynamicActionOptionalParameters("setResponseByteRate", rule, action, optionalParameters);
2✔
935
      group->setResponseByteRate(std::move(rule));
2✔
936
    }
2✔
937
  });
2✔
938
  // NOLINTNEXTLINE(performance-unnecessary-value-param): optional parameters cannot be passed by const reference
939
  luaCtx.registerFunction<void (std::shared_ptr<DynBlockRulesGroup>::*)(unsigned int, const std::string&, unsigned int, boost::optional<DNSAction::Action>, DynBlockRulesGroup::smtVisitor_t, DynamicActionOptionalParameters)>("setSuffixMatchRule", [](std::shared_ptr<DynBlockRulesGroup>& group, unsigned int seconds, const std::string& reason, unsigned int blockDuration, boost::optional<DNSAction::Action> action, DynBlockRulesGroup::smtVisitor_t visitor, DynamicActionOptionalParameters optionalParameters) {
673✔
940
    if (group) {
×
941
      DynBlockRulesGroup::DynBlockRule rule(reason, blockDuration, 0, 0, seconds, action ? *action : DNSAction::Action::None);
×
942
      parseDynamicActionOptionalParameters("setSuffixMatchRule", rule, action, optionalParameters);
×
943
      group->setSuffixMatchRule(std::move(rule), std::move(visitor));
×
944
    }
×
945
  });
×
946
  // NOLINTNEXTLINE(performance-unnecessary-value-param): optional parameters cannot be passed by const reference
947
  luaCtx.registerFunction<void (std::shared_ptr<DynBlockRulesGroup>::*)(unsigned int, const std::string&, unsigned int, boost::optional<DNSAction::Action>, dnsdist_ffi_stat_node_visitor_t, DynamicActionOptionalParameters)>("setSuffixMatchRuleFFI", [](std::shared_ptr<DynBlockRulesGroup>& group, unsigned int seconds, const std::string& reason, unsigned int blockDuration, boost::optional<DNSAction::Action> action, dnsdist_ffi_stat_node_visitor_t visitor, DynamicActionOptionalParameters optionalParameters) {
673✔
948
    if (group) {
×
949
      DynBlockRulesGroup::DynBlockRule rule(reason, blockDuration, 0, 0, seconds, action ? *action : DNSAction::Action::None);
×
950
      parseDynamicActionOptionalParameters("setSuffixMatchRuleFFI", rule, action, optionalParameters);
×
951
      group->setSuffixMatchRuleFFI(std::move(rule), std::move(visitor));
×
952
    }
×
953
  });
×
954
  luaCtx.registerFunction<void (std::shared_ptr<DynBlockRulesGroup>::*)(const dnsdist_ffi_dynamic_block_inserted_hook&)>("setNewBlockInsertedHook", [](std::shared_ptr<DynBlockRulesGroup>& group, const dnsdist_ffi_dynamic_block_inserted_hook& hook) {
673✔
955
    if (group) {
×
956
      group->setNewBlockHook(hook);
×
957
    }
×
958
  });
×
959
  // NOLINTNEXTLINE(performance-unnecessary-value-param): optional parameters cannot be passed by const reference
960
  luaCtx.registerFunction<void (std::shared_ptr<DynBlockRulesGroup>::*)(uint8_t, unsigned int, unsigned int, const std::string&, unsigned int, boost::optional<DNSAction::Action>, boost::optional<unsigned int>, DynamicActionOptionalParameters)>("setRCodeRate", [](std::shared_ptr<DynBlockRulesGroup>& group, uint8_t rcode, unsigned int rate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, boost::optional<DNSAction::Action> action, boost::optional<unsigned int> warningRate, DynamicActionOptionalParameters optionalParameters) {
673✔
961
    if (group) {
2!
962
      DynBlockRulesGroup::DynBlockRule rule(reason, blockDuration, rate, warningRate ? *warningRate : 0, seconds, action ? *action : DNSAction::Action::None);
2!
963
      parseDynamicActionOptionalParameters("setRCodeRate", rule, action, optionalParameters);
2✔
964
      group->setRCodeRate(rcode, std::move(rule));
2✔
965
    }
2✔
966
  });
2✔
967
  // NOLINTNEXTLINE(performance-unnecessary-value-param): optional parameters cannot be passed by const reference
968
  luaCtx.registerFunction<void (std::shared_ptr<DynBlockRulesGroup>::*)(uint8_t, double, unsigned int, const std::string&, unsigned int, size_t, boost::optional<DNSAction::Action>, boost::optional<double>, DynamicActionOptionalParameters)>("setRCodeRatio", [](std::shared_ptr<DynBlockRulesGroup>& group, uint8_t rcode, double ratio, unsigned int seconds, const std::string& reason, unsigned int blockDuration, size_t minimumNumberOfResponses, boost::optional<DNSAction::Action> action, boost::optional<double> warningRatio, DynamicActionOptionalParameters optionalParameters) {
673✔
969
    if (group) {
2!
970
      DynBlockRulesGroup::DynBlockRatioRule rule(reason, blockDuration, ratio, warningRatio ? *warningRatio : 0.0, seconds, action ? *action : DNSAction::Action::None, minimumNumberOfResponses);
2!
971
      parseDynamicActionOptionalParameters("setRCodeRatio", rule, action, optionalParameters);
2✔
972
      group->setRCodeRatio(rcode, std::move(rule));
2✔
973
    }
2✔
974
  });
2✔
975
  // NOLINTNEXTLINE(performance-unnecessary-value-param): optional parameters cannot be passed by const reference
976
  luaCtx.registerFunction<void (std::shared_ptr<DynBlockRulesGroup>::*)(uint16_t, unsigned int, unsigned int, const std::string&, unsigned int, boost::optional<DNSAction::Action>, boost::optional<unsigned int>, DynamicActionOptionalParameters)>("setQTypeRate", [](std::shared_ptr<DynBlockRulesGroup>& group, uint16_t qtype, unsigned int rate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, boost::optional<DNSAction::Action> action, boost::optional<unsigned int> warningRate, DynamicActionOptionalParameters optionalParameters) {
673✔
977
    if (group) {
×
978
      DynBlockRulesGroup::DynBlockRule rule(reason, blockDuration, rate, warningRate ? *warningRate : 0, seconds, action ? *action : DNSAction::Action::None);
×
979
      parseDynamicActionOptionalParameters("setQTypeRate", rule, action, optionalParameters);
×
980
      group->setQTypeRate(qtype, std::move(rule));
×
981
    }
×
982
  });
×
983
  // NOLINTNEXTLINE(performance-unnecessary-value-param): optional parameters cannot be passed by const reference
984
  luaCtx.registerFunction<void (std::shared_ptr<DynBlockRulesGroup>::*)(double, unsigned int, const std::string&, unsigned int, size_t, double, boost::optional<DNSAction::Action>, boost::optional<double>, DynamicActionOptionalParameters)>("setCacheMissRatio", [](std::shared_ptr<DynBlockRulesGroup>& group, double ratio, unsigned int seconds, const std::string& reason, unsigned int blockDuration, size_t minimumNumberOfResponses, double minimumGlobalCacheHitRatio, boost::optional<DNSAction::Action> action, boost::optional<double> warningRatio, DynamicActionOptionalParameters optionalParameters) {
673✔
985
    if (group) {
4!
986
      DynBlockRulesGroup::DynBlockCacheMissRatioRule rule(reason, blockDuration, ratio, warningRatio ? *warningRatio : 0.0, seconds, action ? *action : DNSAction::Action::None, minimumNumberOfResponses, minimumGlobalCacheHitRatio);
4✔
987
      parseDynamicActionOptionalParameters("setCacheMissRatio", rule, action, optionalParameters);
4✔
988
      group->setCacheMissRatio(std::move(rule));
4✔
989
    }
4✔
990
  });
4✔
991
  luaCtx.registerFunction<void (std::shared_ptr<DynBlockRulesGroup>::*)(uint8_t, uint8_t, uint8_t)>("setMasks", [](std::shared_ptr<DynBlockRulesGroup>& group, uint8_t v4addr, uint8_t v6addr, uint8_t port) {
673✔
992
    if (group) {
2!
993
      if (v4addr > 32) {
2!
994
        throw std::runtime_error("Trying to set an invalid IPv4 mask (" + std::to_string(v4addr) + ") to a Dynamic Block object");
×
995
      }
×
996
      if (v6addr > 128) {
2!
997
        throw std::runtime_error("Trying to set an invalid IPv6 mask (" + std::to_string(v6addr) + ") to a Dynamic Block object");
×
998
      }
×
999
      if (port > 16) {
2!
1000
        throw std::runtime_error("Trying to set an invalid port mask (" + std::to_string(port) + ") to a Dynamic Block object");
×
1001
      }
×
1002
      if (port > 0 && v4addr != 32) {
2!
1003
        throw std::runtime_error("Setting a non-zero port mask for Dynamic Blocks while only considering parts of IPv4 addresses does not make sense");
×
1004
      }
×
1005
      group->setMasks(v4addr, v6addr, port);
2✔
1006
    }
2✔
1007
  });
2✔
1008
  luaCtx.registerFunction<void (std::shared_ptr<DynBlockRulesGroup>::*)(boost::variant<std::string, LuaArray<std::string>, NetmaskGroup>)>("excludeRange", [](std::shared_ptr<DynBlockRulesGroup>& group, boost::variant<std::string, LuaArray<std::string>, NetmaskGroup> ranges) {
673✔
1009
    if (ranges.type() == typeid(LuaArray<std::string>)) {
4!
1010
      for (const auto& range : *boost::get<LuaArray<std::string>>(&ranges)) {
×
1011
        group->excludeRange(Netmask(range.second));
×
1012
      }
×
1013
    }
×
1014
    else if (ranges.type() == typeid(NetmaskGroup)) {
4✔
1015
      group->excludeRange(*boost::get<NetmaskGroup>(&ranges));
2✔
1016
    }
2✔
1017
    else {
2✔
1018
      group->excludeRange(Netmask(*boost::get<std::string>(&ranges)));
2✔
1019
    }
2✔
1020
  });
4✔
1021
  luaCtx.registerFunction<void (std::shared_ptr<DynBlockRulesGroup>::*)(boost::variant<std::string, LuaArray<std::string>, NetmaskGroup>)>("includeRange", [](std::shared_ptr<DynBlockRulesGroup>& group, boost::variant<std::string, LuaArray<std::string>, NetmaskGroup> ranges) {
673✔
1022
    if (ranges.type() == typeid(LuaArray<std::string>)) {
×
1023
      for (const auto& range : *boost::get<LuaArray<std::string>>(&ranges)) {
×
1024
        group->includeRange(Netmask(range.second));
×
1025
      }
×
1026
    }
×
1027
    else if (ranges.type() == typeid(NetmaskGroup)) {
×
1028
      group->includeRange(*boost::get<NetmaskGroup>(&ranges));
×
1029
    }
×
1030
    else {
×
1031
      group->includeRange(Netmask(*boost::get<std::string>(&ranges)));
×
1032
    }
×
1033
  });
×
1034
  luaCtx.registerFunction<void (std::shared_ptr<DynBlockRulesGroup>::*)(boost::variant<std::string, LuaArray<std::string>, NetmaskGroup>)>("removeRange", [](std::shared_ptr<DynBlockRulesGroup>& group, boost::variant<std::string, LuaArray<std::string>, NetmaskGroup> ranges) {
673✔
1035
    if (ranges.type() == typeid(LuaArray<std::string>)) {
×
1036
      for (const auto& range : *boost::get<LuaArray<std::string>>(&ranges)) {
×
1037
        group->removeRange(Netmask(range.second));
×
1038
      }
×
1039
    }
×
1040
    else if (ranges.type() == typeid(NetmaskGroup)) {
×
1041
      group->removeRange(*boost::get<NetmaskGroup>(&ranges));
×
1042
    }
×
1043
    else {
×
1044
      group->removeRange(Netmask(*boost::get<std::string>(&ranges)));
×
1045
    }
×
1046
  });
×
1047
  luaCtx.registerFunction<void (std::shared_ptr<DynBlockRulesGroup>::*)(LuaTypeOrArrayOf<std::string>)>("excludeDomains", [](std::shared_ptr<DynBlockRulesGroup>& group, LuaTypeOrArrayOf<std::string> domains) {
673✔
1048
    if (domains.type() == typeid(LuaArray<std::string>)) {
×
1049
      for (const auto& range : *boost::get<LuaArray<std::string>>(&domains)) {
×
1050
        group->excludeDomain(DNSName(range.second));
×
1051
      }
×
1052
    }
×
1053
    else {
×
1054
      group->excludeDomain(DNSName(*boost::get<std::string>(&domains)));
×
1055
    }
×
1056
  });
×
1057
  luaCtx.registerFunction<void (std::shared_ptr<DynBlockRulesGroup>::*)()>("apply", [](std::shared_ptr<DynBlockRulesGroup>& group) {
673✔
1058
    group->apply();
184✔
1059
  });
184✔
1060
  luaCtx.registerFunction("setQuiet", &DynBlockRulesGroup::setQuiet);
673✔
1061
  luaCtx.registerFunction("toString", &DynBlockRulesGroup::toString);
673✔
1062

1063
  /* DynBlock object accessors */
1064
  luaCtx.registerMember("reason", &DynBlock::reason);
673✔
1065
  luaCtx.registerMember("domain", &DynBlock::domain);
673✔
1066
  luaCtx.registerMember<DynBlock, timespec>(
673✔
1067
    "until", [](const DynBlock& block) {
673✔
1068
      timespec nowMonotonic{};
×
1069
      gettime(&nowMonotonic);
×
1070
      timespec nowRealTime{};
×
1071
      gettime(&nowRealTime, true);
×
1072

1073
      auto seconds = block.until.tv_sec - nowMonotonic.tv_sec;
×
1074
      auto nseconds = block.until.tv_nsec - nowMonotonic.tv_nsec;
×
1075
      if (nseconds < 0) {
×
1076
        seconds -= 1;
×
1077
        nseconds += 1000000000;
×
1078
      }
×
1079

1080
      nowRealTime.tv_sec += seconds;
×
1081
      nowRealTime.tv_nsec += nseconds;
×
1082
      if (nowRealTime.tv_nsec > 1000000000) {
×
1083
        nowRealTime.tv_sec += 1;
×
1084
        nowRealTime.tv_nsec -= 1000000000;
×
1085
      }
×
1086

NEW
1087
      return nowRealTime; }, []([[maybe_unused]] DynBlock& block, [[maybe_unused]] timespec until) {});
×
1088
  luaCtx.registerMember<DynBlock, unsigned int>(
673✔
1089
    "blocks", [](const DynBlock& block) { return block.blocks.load(); }, []([[maybe_unused]] DynBlock& block, [[maybe_unused]] unsigned int blocks) {});
673✔
1090
  luaCtx.registerMember("action", &DynBlock::action);
673✔
1091
  luaCtx.registerMember("warning", &DynBlock::warning);
673✔
1092
  luaCtx.registerMember("bpf", &DynBlock::bpf);
673✔
1093

1094
  luaCtx.writeFunction("addDynBlockSMT",
673✔
1095
                       // NOLINTNEXTLINE(performance-unnecessary-value-param): optional parameters cannot be passed by const reference
1096
                       [](const LuaArray<std::string>& names, const std::string& msg, boost::optional<int> seconds, boost::optional<DNSAction::Action> action, DynamicActionOptionalParameters optionalParameters) {
673✔
1097
                         if (names.empty()) {
×
1098
                           return;
×
1099
                         }
×
1100
                         setLuaSideEffect();
×
1101
                         timespec now{};
×
1102
                         gettime(&now);
×
1103
                         unsigned int actualSeconds = seconds ? *seconds : 10;
×
1104
                         DynBlockRulesGroup::DynBlockRule rule;
×
1105
                         parseDynamicActionOptionalParameters("addDynBlockSMT", rule, action, optionalParameters);
×
1106

1107
                         bool needUpdate = false;
×
1108
                         auto smtBlocks = dnsdist::DynamicBlocks::getSuffixDynamicRulesCopy();
×
1109
                         for (const auto& capair : names) {
×
1110
                           DNSName domain(capair.second);
×
1111
                           domain.makeUsLowerCase();
×
1112
                           timespec until{now};
×
1113
                           until.tv_sec += actualSeconds;
×
1114
                           DynBlock dblock{msg, until, domain, action ? *action : DNSAction::Action::None};
×
1115
                           dblock.tagSettings = rule.d_tagSettings;
×
1116
                           if (dnsdist::DynamicBlocks::addOrRefreshBlockSMT(smtBlocks, now, std::move(dblock), false)) {
×
1117
                             needUpdate = true;
×
1118
                           }
×
1119
                         }
×
1120

1121
                         if (needUpdate) {
×
1122
                           dnsdist::DynamicBlocks::setSuffixDynamicRules(std::move(smtBlocks));
×
1123
                         }
×
1124
                       });
×
1125

1126
  luaCtx.writeFunction("addDynamicBlock",
673✔
1127
                       // NOLINTNEXTLINE(performance-unnecessary-value-param): optional parameters cannot be passed by const reference
1128
                       [](const boost::variant<ComboAddress, std::string>& clientIP, const std::string& msg, const boost::optional<DNSAction::Action> action, const boost::optional<int> seconds, boost::optional<uint8_t> clientIPMask, boost::optional<uint8_t> clientIPPortMask, DynamicActionOptionalParameters optionalParameters) {
673✔
1129
                         setLuaSideEffect();
×
1130

1131
                         ComboAddress clientIPCA;
×
1132
                         if (clientIP.type() == typeid(ComboAddress)) {
×
1133
                           clientIPCA = boost::get<ComboAddress>(clientIP);
×
1134
                         }
×
1135
                         else {
×
1136
                           const auto& clientIPStr = boost::get<std::string>(clientIP);
×
1137
                           try {
×
1138
                             clientIPCA = ComboAddress(clientIPStr);
×
1139
                           }
×
1140
                           catch (const std::exception& exp) {
×
1141
                             errlog("addDynamicBlock: Unable to parse '%s': %s", clientIPStr, exp.what());
×
1142
                             return;
×
1143
                           }
×
1144
                           catch (const PDNSException& exp) {
×
1145
                             errlog("addDynamicBlock: Unable to parse '%s': %s", clientIPStr, exp.reason);
×
1146
                             return;
×
1147
                           }
×
1148
                         }
×
1149
                         AddressAndPortRange target(clientIPCA, clientIPMask ? *clientIPMask : (clientIPCA.isIPv4() ? 32 : 128), clientIPPortMask ? *clientIPPortMask : 0);
×
1150
                         unsigned int actualSeconds = seconds ? *seconds : 10;
×
1151
                         DynBlockRulesGroup::DynBlockRule rule;
×
1152
                         parseDynamicActionOptionalParameters("addDynBlockSMT", rule, action, optionalParameters);
×
1153

1154
                         timespec now{};
×
1155
                         gettime(&now);
×
1156
                         timespec until{now};
×
1157
                         until.tv_sec += actualSeconds;
×
1158
                         DynBlock dblock{msg, until, DNSName(), action ? *action : DNSAction::Action::None};
×
1159
                         dblock.tagSettings = rule.d_tagSettings;
×
1160

1161
                         auto dynamicRules = dnsdist::DynamicBlocks::getClientAddressDynamicRulesCopy();
×
1162
                         if (dnsdist::DynamicBlocks::addOrRefreshBlock(dynamicRules, now, target, std::move(dblock), false)) {
×
1163
                           dnsdist::DynamicBlocks::setClientAddressDynamicRules(std::move(dynamicRules));
×
1164
                         }
×
1165
                       });
×
1166
#endif /* DISABLE_DYNBLOCKS */
673✔
1167
}
673✔
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