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

PowerDNS / pdns / 19741624072

27 Nov 2025 03:45PM UTC coverage: 73.086% (+0.02%) from 73.065%
19741624072

Pull #16570

github

web-flow
Merge 08a2cdb1d into f94a3f63f
Pull Request #16570: rec: rewrite all unwrap calls in web.rs

38523 of 63408 branches covered (60.75%)

Branch coverage included in aggregate %.

128044 of 164496 relevant lines covered (77.84%)

6531485.83 hits per line

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

68.51
/pdns/dnsdistdist/dnsdist-dynblocks.hh
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
#pragma once
23

24
#ifndef DISABLE_DYNBLOCKS
25
#include <unordered_set>
26

27
#include "dolog.hh"
28
#include "dnsdist-rings.hh"
29
#include "gettime.hh"
30
#include "statnode.hh"
31

32
extern "C"
33
{
34
#include "dnsdist-lua-inspection-ffi.h"
35
}
36

37
#include "ext/luawrapper/include/LuaContext.hpp"
38

39
// dnsdist_ffi_stat_node_t is a lightuserdata
40
template <>
41
struct LuaContext::Pusher<dnsdist_ffi_stat_node_t*>
42
{
43
  static const int minSize = 1;
44
  static const int maxSize = 1;
45

46
  static PushedObject push(lua_State* state, dnsdist_ffi_stat_node_t* ptr) noexcept
47
  {
×
48
    lua_pushlightuserdata(state, ptr);
×
49
    return PushedObject{state, 1};
×
50
  }
×
51
};
52

53
using dnsdist_ffi_stat_node_visitor_t = std::function<bool(dnsdist_ffi_stat_node_t*)>;
54

55
struct SMTBlockParameters
56
{
57
  std::optional<std::string> d_reason;
58
  std::optional<DNSAction::Action> d_action;
59
};
60

61
struct dnsdist_ffi_stat_node_t
62
{
63
  dnsdist_ffi_stat_node_t(const StatNode& node_, const StatNode::Stat& self_, const StatNode::Stat& children_, SMTBlockParameters& blockParameters) :
64
    node(node_), self(self_), children(children_), d_blockParameters(blockParameters)
×
65
  {
×
66
  }
×
67

68
  const StatNode& node;
69
  const StatNode::Stat& self;
70
  const StatNode::Stat& children;
71
  SMTBlockParameters& d_blockParameters;
72
};
73

74
struct DynBlock
75
{
76
  DynBlock()
77
  {
14,832✔
78
    until.tv_sec = 0;
14,832✔
79
    until.tv_nsec = 0;
14,832✔
80
  }
14,832✔
81

82
  DynBlock(const std::string& reason_, const struct timespec& until_, const DNSName& domain_, DNSAction::Action action_) :
83
    reason(reason_), domain(domain_), until(until_), action(action_)
1,666✔
84
  {
1,666✔
85
  }
1,666✔
86

87
  DynBlock(const DynBlock& rhs) :
88
    reason(rhs.reason), domain(rhs.domain), until(rhs.until), tagSettings(rhs.tagSettings), action(rhs.action), warning(rhs.warning), bpf(rhs.bpf)
×
89
  {
×
90
    blocks.store(rhs.blocks);
×
91
  }
×
92

93
  DynBlock(DynBlock&& rhs) :
94
    reason(std::move(rhs.reason)), domain(std::move(rhs.domain)), until(rhs.until), tagSettings(std::move(rhs.tagSettings)), action(rhs.action), warning(rhs.warning), bpf(rhs.bpf)
2,166✔
95
  {
2,166✔
96
    blocks.store(rhs.blocks);
2,166✔
97
  }
2,166✔
98

99
  DynBlock& operator=(const DynBlock& rhs)
100
  {
2,632✔
101
    reason = rhs.reason;
2,632✔
102
    until = rhs.until;
2,632✔
103
    domain = rhs.domain;
2,632✔
104
    action = rhs.action;
2,632✔
105
    blocks.store(rhs.blocks);
2,632✔
106
    warning = rhs.warning;
2,632✔
107
    bpf = rhs.bpf;
2,632✔
108
    tagSettings = rhs.tagSettings;
2,632✔
109
    return *this;
2,632✔
110
  }
2,632✔
111

112
  DynBlock& operator=(DynBlock&& rhs)
113
  {
2,175✔
114
    reason = std::move(rhs.reason);
2,175✔
115
    until = rhs.until;
2,175✔
116
    domain = std::move(rhs.domain);
2,175✔
117
    action = rhs.action;
2,175✔
118
    blocks.store(rhs.blocks);
2,175✔
119
    warning = rhs.warning;
2,175✔
120
    bpf = rhs.bpf;
2,175✔
121
    tagSettings = std::move(rhs.tagSettings);
2,175✔
122
    return *this;
2,175✔
123
  }
2,175✔
124

125
  struct TagSettings
126
  {
127
    std::string d_name;
128
    std::string d_value;
129
  };
130

131
  string reason;
132
  DNSName domain;
133
  timespec until{};
134
  std::shared_ptr<TagSettings> tagSettings{nullptr};
135
  mutable std::atomic<uint32_t> blocks{0};
136
  DNSAction::Action action{DNSAction::Action::None};
137
  bool warning{false};
138
  bool bpf{false};
139
};
140

141
using dnsdist_ffi_dynamic_block_inserted_hook = std::function<void(uint8_t type, const char* key, const char* reason, uint8_t action, uint64_t duration, bool warning)>;
142
using ClientAddressDynamicRules = NetmaskTree<DynBlock, AddressAndPortRange>;
143
using SuffixDynamicRules = SuffixMatchTree<DynBlock>;
144

145
class DynBlockRulesGroup
146
{
147
public:
148
  struct DynBlockRule
149
  {
150
    DynBlockRule() = default;
274✔
151
    DynBlockRule(const std::string& blockReason, unsigned int blockDuration, unsigned int rate, unsigned int warningRate, unsigned int seconds, DNSAction::Action action) :
152
      d_blockReason(blockReason), d_blockDuration(blockDuration), d_rate(rate), d_warningRate(warningRate), d_seconds(seconds), d_action(action), d_enabled(true)
66✔
153
    {
66✔
154
    }
66✔
155

156
    bool matches(const struct timespec& when);
157
    bool rateExceeded(unsigned int count, const struct timespec& now) const;
158
    bool warningRateExceeded(unsigned int count, const struct timespec& now) const;
159

160
    bool isEnabled() const
161
    {
1,734✔
162
      return d_enabled;
1,734✔
163
    }
1,734✔
164

165
    std::string toString() const;
166

167
    std::string d_blockReason;
168
    std::shared_ptr<DynBlock::TagSettings> d_tagSettings;
169
    struct timespec d_cutOff;
170
    struct timespec d_minTime;
171
    unsigned int d_blockDuration{0};
172
    unsigned int d_rate{0};
173
    unsigned int d_warningRate{0};
174
    unsigned int d_seconds{0};
175
    DNSAction::Action d_action{DNSAction::Action::None};
176
    bool d_enabled{false};
177
  };
178

179
  struct DynBlockRatioRule : DynBlockRule
180
  {
181
    DynBlockRatioRule() = default;
68✔
182
    DynBlockRatioRule(const std::string& blockReason, unsigned int blockDuration, double ratio, double warningRatio, unsigned int seconds, DNSAction::Action action, size_t minimumNumberOfResponses) :
183
      DynBlockRule(blockReason, blockDuration, 0, 0, seconds, action), d_minimumNumberOfResponses(minimumNumberOfResponses), d_ratio(ratio), d_warningRatio(warningRatio)
10✔
184
    {
10✔
185
    }
10✔
186

187
    bool ratioExceeded(unsigned int total, unsigned int count) const;
188
    bool warningRatioExceeded(unsigned int total, unsigned int count) const;
189
    std::string toString() const;
190

191
    size_t d_minimumNumberOfResponses{0};
192
    double d_ratio{0.0};
193
    double d_warningRatio{0.0};
194
  };
195

196
  struct DynBlockCacheMissRatioRule : public DynBlockRatioRule
197
  {
198
    DynBlockCacheMissRatioRule() = default;
64✔
199
    DynBlockCacheMissRatioRule(const std::string& blockReason, unsigned int blockDuration, double ratio, double warningRatio, unsigned int seconds, DNSAction::Action action, size_t minimumNumberOfResponses, double minimumGlobalCacheHitRatio) :
200
      DynBlockRatioRule(blockReason, blockDuration, ratio, warningRatio, seconds, action, minimumNumberOfResponses), d_minimumGlobalCacheHitRatio(minimumGlobalCacheHitRatio)
6✔
201
    {
6✔
202
    }
6✔
203

204
    bool checkGlobalCacheHitRatio() const;
205
    bool ratioExceeded(unsigned int total, unsigned int count) const;
206
    bool warningRatioExceeded(unsigned int total, unsigned int count) const;
207
    std::string toString() const;
208

209
    double d_minimumGlobalCacheHitRatio{0.0};
210
  };
211

212
private:
213
  struct Counts
214
  {
215
    std::map<uint8_t, uint64_t> d_rcodeCounts;
216
    std::map<uint16_t, uint64_t> d_qtypeCounts;
217
    uint64_t queries{0};
218
    uint64_t responses{0};
219
    uint64_t respBytes{0};
220
    uint64_t cacheMisses{0};
221
  };
222
  using counts_t = std::unordered_map<AddressAndPortRange, Counts, AddressAndPortRange::hash>;
223

224
public:
225
  DynBlockRulesGroup()
226
  {
64✔
227
  }
64✔
228

229
  void setQueryRate(DynBlockRule&& rule)
230
  {
32✔
231
    d_queryRateRule = std::move(rule);
32✔
232
  }
32✔
233

234
  /* rate is in bytes per second */
235
  void setResponseByteRate(DynBlockRule&& rule)
236
  {
4✔
237
    d_respRateRule = std::move(rule);
4✔
238
  }
4✔
239

240
  void setRCodeRate(uint8_t rcode, DynBlockRule&& rule)
241
  {
8✔
242
    d_rcodeRules[rcode] = std::move(rule);
8✔
243
  }
8✔
244

245
  void setRCodeRatio(uint8_t rcode, DynBlockRatioRule&& rule)
246
  {
4✔
247
    d_rcodeRatioRules[rcode] = std::move(rule);
4✔
248
  }
4✔
249

250
  void setQTypeRate(uint16_t qtype, DynBlockRule&& rule)
251
  {
6✔
252
    d_qtypeRules[qtype] = std::move(rule);
6✔
253
  }
6✔
254

255
  void setCacheMissRatio(DynBlockCacheMissRatioRule&& rule)
256
  {
6✔
257
    d_respCacheMissRatioRule = std::move(rule);
6✔
258
  }
6✔
259

260
  using smtVisitor_t = std::function<std::tuple<bool, boost::optional<std::string>, boost::optional<int>>(const StatNode&, const StatNode::Stat&, const StatNode::Stat&)>;
261

262
  void setSuffixMatchRule(DynBlockRule&& rule, smtVisitor_t visitor)
263
  {
6✔
264
    d_suffixMatchRule = std::move(rule);
6✔
265
    d_smtVisitor = std::move(visitor);
6✔
266
  }
6✔
267

268
  void setSuffixMatchRuleFFI(DynBlockRule&& rule, dnsdist_ffi_stat_node_visitor_t visitor)
269
  {
×
270
    d_suffixMatchRule = std::move(rule);
×
271
    d_smtVisitorFFI = std::move(visitor);
×
272
  }
×
273

274
  void setNewBlockHook(const dnsdist_ffi_dynamic_block_inserted_hook& callback)
275
  {
×
276
    d_newBlockHook = callback;
×
277
  }
×
278

279
  void setMasks(uint8_t v4, uint8_t v6, uint8_t port)
280
  {
12✔
281
    d_v4Mask = v4;
12✔
282
    d_v6Mask = v6;
12✔
283
    d_portMask = port;
12✔
284
  }
12✔
285

286
  void apply()
287
  {
192✔
288
    timespec now{};
192✔
289
    gettime(&now);
192✔
290

291
    apply(now);
192✔
292
  }
192✔
293

294
  void apply(const timespec& now);
295

296
  void excludeRange(const Netmask& range)
297
  {
8✔
298
    d_excludedSubnets.addMask(range);
8✔
299
  }
8✔
300

301
  void excludeRange(const NetmaskGroup& group)
302
  {
2✔
303
    d_excludedSubnets.addMasks(group, true);
2✔
304
  }
2✔
305

306
  void includeRange(const Netmask& range)
307
  {
4✔
308
    d_excludedSubnets.addMask(range, false);
4✔
309
  }
4✔
310

311
  void includeRange(const NetmaskGroup& group)
312
  {
×
313
    d_excludedSubnets.addMasks(group, false);
×
314
  }
×
315

316
  void removeRange(const Netmask& range)
317
  {
×
318
    d_excludedSubnets.deleteMask(range);
×
319
  }
×
320

321
  void removeRange(const NetmaskGroup& group)
322
  {
×
323
    d_excludedSubnets.deleteMasks(group);
×
324
  }
×
325

326
  void excludeDomain(const DNSName& domain)
327
  {
2✔
328
    d_excludedDomains.add(domain);
2✔
329
  }
2✔
330

331
  std::string toString() const
332
  {
×
333
    std::stringstream result;
×
334

335
    result << "Query rate rule: " << d_queryRateRule.toString() << std::endl;
×
336
    result << "Response rate rule: " << d_respRateRule.toString() << std::endl;
×
337
    result << "SuffixMatch rule: " << d_suffixMatchRule.toString() << std::endl;
×
338
    result << "Response cache-miss ratio rule: " << d_respCacheMissRatioRule.toString() << std::endl;
×
339
    result << "RCode rules: " << std::endl;
×
340
    for (const auto& rule : d_rcodeRules) {
×
341
      result << "- " << RCode::to_s(rule.first) << ": " << rule.second.toString() << std::endl;
×
342
    }
×
343
    for (const auto& rule : d_rcodeRatioRules) {
×
344
      result << "- " << RCode::to_s(rule.first) << ": " << rule.second.toString() << std::endl;
×
345
    }
×
346
    result << "QType rules: " << std::endl;
×
347
    for (const auto& rule : d_qtypeRules) {
×
348
      result << "- " << QType(rule.first).toString() << ": " << rule.second.toString() << std::endl;
×
349
    }
×
350
    result << "Excluded Subnets: " << d_excludedSubnets.toString() << std::endl;
×
351
    result << "Excluded Domains: " << d_excludedDomains.toString() << std::endl;
×
352

353
    return result.str();
×
354
  }
×
355

356
  void setQuiet(bool quiet)
357
  {
28✔
358
    d_beQuiet = quiet;
28✔
359
  }
28✔
360

361
private:
362
  void applySMT(const struct timespec& now, StatNode& statNodeRoot);
363
  bool checkIfQueryTypeMatches(const Rings::Query& query);
364
  bool checkIfResponseCodeMatches(const Rings::Response& response);
365
  void addOrRefreshBlock(boost::optional<ClientAddressDynamicRules>& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const DynBlockRule& rule, bool& updated, bool warning);
366
  void addOrRefreshBlockSMT(SuffixDynamicRules& blocks, const struct timespec& now, const DNSName& name, const DynBlockRule& rule, bool& updated);
367

368
  void addBlock(boost::optional<ClientAddressDynamicRules>& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const DynBlockRule& rule, bool& updated)
369
  {
604✔
370
    addOrRefreshBlock(blocks, now, requestor, rule, updated, false);
604✔
371
  }
604✔
372

373
  void handleWarning(boost::optional<ClientAddressDynamicRules>& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const DynBlockRule& rule, bool& updated)
374
  {
14✔
375
    addOrRefreshBlock(blocks, now, requestor, rule, updated, true);
14✔
376
  }
14✔
377

378
  bool hasQueryRules() const
379
  {
598✔
380
    return d_queryRateRule.isEnabled() || !d_qtypeRules.empty();
598✔
381
  }
598✔
382

383
  bool hasResponseRules() const
384
  {
598✔
385
    return d_respRateRule.isEnabled() || !d_rcodeRules.empty() || !d_rcodeRatioRules.empty() || d_respCacheMissRatioRule.isEnabled();
598✔
386
  }
598✔
387

388
  bool hasSuffixMatchRules() const
389
  {
158✔
390
    return d_suffixMatchRule.isEnabled();
158✔
391
  }
158✔
392

393
  bool hasRules() const
394
  {
×
395
    return hasQueryRules() || hasResponseRules();
×
396
  }
×
397

398
  void processQueryRules(counts_t& counts, const struct timespec& now);
399
  void processResponseRules(counts_t& counts, StatNode& root, const struct timespec& now);
400

401
  std::map<uint8_t, DynBlockRule> d_rcodeRules;
402
  std::map<uint8_t, DynBlockRatioRule> d_rcodeRatioRules;
403
  std::map<uint16_t, DynBlockRule> d_qtypeRules;
404
  DynBlockRule d_queryRateRule;
405
  DynBlockRule d_respRateRule;
406
  DynBlockRule d_suffixMatchRule;
407
  DynBlockCacheMissRatioRule d_respCacheMissRatioRule;
408
  NetmaskGroup d_excludedSubnets;
409
  SuffixMatchNode d_excludedDomains;
410
  smtVisitor_t d_smtVisitor;
411
  dnsdist_ffi_stat_node_visitor_t d_smtVisitorFFI;
412
  dnsdist_ffi_dynamic_block_inserted_hook d_newBlockHook;
413
  uint8_t d_v6Mask{128};
414
  uint8_t d_v4Mask{32};
415
  uint8_t d_portMask{0};
416
  bool d_beQuiet{false};
417
};
418

419
class DynBlockMaintenance
420
{
421
public:
422
  static void run();
423

424
  /* return the (cached) number of hits per second for the top offenders, averaged over 60s */
425
  static std::map<std::string, std::list<std::pair<AddressAndPortRange, unsigned int>>> getHitsForTopNetmasks();
426
  static std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> getHitsForTopSuffixes();
427

428
  /* get the top offenders based on the current value of the counters */
429
  static std::map<std::string, std::list<std::pair<AddressAndPortRange, unsigned int>>> getTopNetmasks(size_t topN);
430
  static std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> getTopSuffixes(size_t topN);
431
  static void purgeExpired(const struct timespec& now);
432

433
private:
434
  static void collectMetrics();
435
  static void generateMetrics();
436

437
  struct MetricsSnapshot
438
  {
439
    std::map<std::string, std::list<std::pair<AddressAndPortRange, unsigned int>>> nmgData;
440
    std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> smtData;
441
  };
442

443
  struct Tops
444
  {
445
    std::map<std::string, std::list<std::pair<AddressAndPortRange, unsigned int>>> topNMGsByReason;
446
    std::map<std::string, std::list<std::pair<DNSName, unsigned int>>> topSMTsByReason;
447
  };
448

449
  static LockGuarded<Tops> s_tops;
450
  /* s_metricsData should only be accessed by the dynamic blocks maintenance thread so it does not need a lock */
451
  // need N+1 datapoints to be able to do the diff after a collection point has been reached
452
  static std::list<MetricsSnapshot> s_metricsData;
453
  static constexpr size_t s_topN{20};
454
};
455

456
namespace dnsdist::DynamicBlocks
457
{
458
bool addOrRefreshBlock(ClientAddressDynamicRules& blocks, const timespec& now, const AddressAndPortRange& requestor, DynBlock&& dblock, bool beQuiet);
459
bool addOrRefreshBlockSMT(SuffixDynamicRules& blocks, const timespec& now, DynBlock&& dblock, bool beQuiet);
460

461
const ClientAddressDynamicRules& getClientAddressDynamicRules();
462
const SuffixDynamicRules& getSuffixDynamicRules();
463
ClientAddressDynamicRules getClientAddressDynamicRulesCopy();
464
SuffixDynamicRules getSuffixDynamicRulesCopy();
465
void setClientAddressDynamicRules(ClientAddressDynamicRules&& rules);
466
void setSuffixDynamicRules(SuffixDynamicRules&& rules);
467
void clearClientAddressDynamicRules();
468
void clearSuffixDynamicRules();
469

470
void registerGroup(std::shared_ptr<DynBlockRulesGroup>& group);
471
void runRegisteredGroups(LuaContext& luaCtx);
472
}
473
#endif /* DISABLE_DYNBLOCKS */
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