struct dnsdist_ffi_stat_node_t
{
- dnsdist_ffi_stat_node_t(const StatNode& node_, const StatNode::Stat& self_, const StatNode::Stat& children_): node(node_), self(self_), children(children_)
+ dnsdist_ffi_stat_node_t(const StatNode& node_, const StatNode::Stat& self_, const StatNode::Stat& children_, std::optional<std::string>& reason_): node(node_), self(self_), children(children_), reason(reason_)
{
}
const StatNode& node;
const StatNode::Stat& self;
const StatNode::Stat& children;
+ std::optional<std::string>& reason;
};
class DynBlockRulesGroup
entry = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action);
}
- typedef std::function<bool(const StatNode&, const StatNode::Stat&, const StatNode::Stat&)> smtVisitor_t;
+ typedef std::function<std::tuple<bool, boost::optional<std::string>>(const StatNode&, const StatNode::Stat&, const StatNode::Stat&)> smtVisitor_t;
void setSuffixMatchRule(unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action, smtVisitor_t visitor)
{
if (!statNodeRoot.empty()) {
StatNode::Stat node;
- std::unordered_set<DNSName> namesToBlock;
+ std::unordered_map<DNSName, std::optional<std::string>> namesToBlock;
statNodeRoot.visit([this,&namesToBlock](const StatNode* node_, const StatNode::Stat& self, const StatNode::Stat& children) {
bool block = false;
+ std::optional<std::string> reason;
if (d_smtVisitorFFI) {
- dnsdist_ffi_stat_node_t tmp(*node_, self, children);
+ dnsdist_ffi_stat_node_t tmp(*node_, self, children, reason);
block = d_smtVisitorFFI(&tmp);
}
else {
- block = d_smtVisitor(*node_, self, children);
+ auto ret = d_smtVisitor(*node_, self, children);
+ block = std::get<0>(ret);
+ if (block) {
+ if (boost::optional<std::string> tmp = std::get<1>(ret)) {
+ reason = std::move(*tmp);
+ }
+ }
}
if (block) {
- namesToBlock.insert(DNSName(node_->fullname));
+ namesToBlock.insert({DNSName(node_->fullname), std::move(reason)});
}
},
node);
if (!namesToBlock.empty()) {
updated = false;
SuffixMatchTree<DynBlock> smtBlocks = g_dynblockSMT.getCopy();
- for (const auto& name : namesToBlock) {
- addOrRefreshBlockSMT(smtBlocks, now, name, d_suffixMatchRule, updated);
+ for (auto& [name, reason] : namesToBlock) {
+ if (reason) {
+ DynBlockRule rule(d_suffixMatchRule);
+ rule.d_blockReason = std::move(*reason);
+ addOrRefreshBlockSMT(smtBlocks, now, std::move(name), std::move(rule), updated);
+ }
+ else {
+ addOrRefreshBlockSMT(smtBlocks, now, std::move(name), d_suffixMatchRule, updated);
+ }
}
if (updated) {
g_dynblockSMT.setState(std::move(smtBlocks));
{
return node->children.bytes;
}
+
+void dnsdist_ffi_state_node_set_reason(dnsdist_ffi_stat_node_t* node, const char* reason, size_t reasonSize)
+{
+ node->reason = std::string(reason, reasonSize);
+}
uint64_t dnsdist_ffi_stat_node_get_children_servfails_count(const dnsdist_ffi_stat_node_t* node) __attribute__ ((visibility ("default")));
uint64_t dnsdist_ffi_stat_node_get_children_drops_count(const dnsdist_ffi_stat_node_t* node) __attribute__ ((visibility ("default")));
uint64_t dnsdist_ffi_stat_node_get_children_bytes_count(const dnsdist_ffi_stat_node_t* node) __attribute__ ((visibility ("default")));
+
+ void dnsdist_ffi_state_node_set_reason(dnsdist_ffi_stat_node_t* node, const char* reason, size_t reasonSize) __attribute__ ((visibility ("default")));
}
.. versionadded:: 1.4.0
+ .. versionchanged:: 1.7.0
+ This visitor function can now optionally return an additional string which will be set as the ``reason`` for the dynamic block.
+
Set a Lua visitor function that will be called for each label of every domain seen in queries and responses. The function receives a `StatNode` object representing the stats of the parent, a second one with the stats of the current label and one with the stats of the current node plus all its children.
Note that this function will not be called if a FFI version has been set using :meth:`DynBlockRulesGroup:setSuffixMatchRuleFFI`
- If the function returns true, the current label will be blocked according to the `seconds`, `reason`, `blockingTime` and `action` parameters.
+ If the function returns true, the current label will be blocked according to the `seconds`, `reason`, `blockingTime` and `action` parameters. Since 1.7.0, the function can return an additional string, in addition to the boolean, which will be set as the ``reason`` for the dynamic block.
Selected domains can be excluded from this processing using the :meth:`DynBlockRulesGroup:excludeDomains` method.
This replaces the existing :func:`addDynBlockSMT` function.
dbrg.setSuffixMatchRule(numberOfSeconds, reason, blockDuration, action, [](const StatNode& node, const StatNode::Stat& self, const StatNode::Stat& children) {
if (self.queries > 0) {
- return true;
+ return std::tuple<bool, boost::optional<std::string>>(true, boost::none);
}
- return false;
+ return std::tuple<bool, boost::optional<std::string>>(false, boost::none);
});
/* insert one fake response for 255 DNS names */
BOOST_CHECK(g_dynblockSMT.getLocal()->getNodes().empty());
}
+ {
+ /* === reset everything for SMT, this time we will check that we can override the 'reason' via the visitor function === */
+ DynBlockRulesGroup dbrg;
+ dbrg.setQuiet(true);
+ g_rings.clear();
+ g_dynblockNMG.setState(emptyNMG);
+ g_dynblockSMT.setState(emptySMT);
+
+ dbrg.setSuffixMatchRule(numberOfSeconds, reason, blockDuration, action, [](const StatNode& node, const StatNode::Stat& self, const StatNode::Stat& children) {
+ if (self.queries > 0) {
+ return std::tuple<bool, boost::optional<std::string>>(true, "blocked for a different reason");
+ }
+ return std::tuple<bool, boost::optional<std::string>>(false, boost::none);
+ });
+
+ /* insert one fake response for 255 DNS names */
+ const ComboAddress requestor("192.0.2.1");
+ for (size_t idx = 0; idx < 256; idx++) {
+ g_rings.insertResponse(now, requestor, DNSName(std::to_string(idx)) + qname, qtype, 1000 /*usec*/, size, dh, requestor /* backend, technically, but we don't care */);
+ }
+
+ /* we apply the rules, all suffixes should be blocked */
+ dbrg.apply(now);
+
+ for (size_t idx = 0; idx < 256; idx++) {
+ const DNSName name(DNSName(std::to_string(idx)) + qname);
+ const auto* block = g_dynblockSMT.getLocal()->lookup(name);
+ BOOST_REQUIRE(block != nullptr);
+ /* simulate that:
+ - 1.rings.powerdns.com. got 1 query
+ ...
+ - 255. does 255 queries
+ */
+ block->blocks = idx;
+ }
+
+ /* now we ask for the top 20 offenders for each reason */
+ StopWatch sw;
+ sw.start();
+ auto top = DynBlockMaintenance::getTopSuffixes(20);
+ BOOST_REQUIRE_EQUAL(top.size(), 1U);
+ auto suffixes = top.at("blocked for a different reason");
+ BOOST_REQUIRE_EQUAL(suffixes.size(), 20U);
+ auto it = suffixes.begin();
+ for (size_t idx = 236; idx < 256; idx++) {
+ BOOST_CHECK_EQUAL(it->first, (DNSName(std::to_string(idx)) + qname));
+ BOOST_CHECK_EQUAL(it->second, idx);
+ ++it;
+ }
+
+ struct timespec expired = now;
+ expired.tv_sec += blockDuration + 1;
+ DynBlockMaintenance::purgeExpired(expired);
+ BOOST_CHECK(g_dynblockSMT.getLocal()->getNodes().empty());
+ }
+
#ifdef BENCH_DYNBLOCKS
{
/* now insert 1M names */
dbrg.setSuffixMatchRule(numberOfSeconds, reason, blockDuration, action, [](const StatNode& node, const StatNode::Stat& self, const StatNode::Stat& children) {
if (self.queries > 0) {
- return true;
+ return std::tuple<bool, boost::optional<std::string>>(true, boost::none);
}
- return false;
+ return std::tuple<bool, boost::optional<std::string>>(false, boost::none);
});
bool done = false;
sw.start();
auto top = DynBlockMaintenance::getTopSuffixes(20);
cerr<<"scanned 1000000 entries in "<<std::to_string(sw.udiff()/1024)<<"ms"<<endl;
+ BOOST_CHECK_EQUAL(top.at(reason).size(), 20U);
+ BOOST_CHECK_EQUAL(top.size(), 1U);
struct timespec expired = now;
expired.tv_sec += blockDuration + 1;
sw.start();
DynBlockMaintenance::purgeExpired(expired);
cerr<<"removed 1000000 entries in "<<std::to_string(sw.udiff()/1024)<<"ms"<<endl;
+ BOOST_CHECK_EQUAL(g_dynblockSMT.getLocal()->getNodes().size(), 0U);
}
#endif