*/
#pragma once
+#include <unordered_set>
+
#include "dolog.hh"
#include "dnsdist-rings.hh"
+#include "statnode.hh"
class DynBlockRulesGroup
{
bool d_enabled{false};
};
- typedef std::unordered_map<ComboAddress, Counts, ComboAddress::addressOnlyHash, ComboAddress::addressOnlyEqual> counts_t;
+ typedef std::unordered_map<ComboAddress, Counts, ComboAddress::addressOnlyHash, ComboAddress::addressOnlyEqual> counts_t;
public:
DynBlockRulesGroup()
entry = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action);
}
+ typedef std::function<bool(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)
+ {
+ d_suffixMatchRule = DynBlockRule(reason, blockDuration, 0, 0, seconds, action);
+ d_smtVisitor = visitor;
+ }
+
void apply()
{
struct timespec now;
void apply(const struct timespec& now)
{
counts_t counts;
+ StatNode statNodeRoot;
size_t entriesCount = 0;
if (hasQueryRules()) {
counts.reserve(entriesCount);
processQueryRules(counts, now);
- processResponseRules(counts, now);
+ processResponseRules(counts, statNodeRoot, now);
- if (counts.empty()) {
+ if (counts.empty() && statNodeRoot.empty()) {
return;
}
if (updated && blocks) {
g_dynblockNMG.setState(*blocks);
}
+
+ if (!statNodeRoot.empty()) {
+ StatNode::Stat node;
+ std::unordered_set<DNSName> namesToBlock;
+ statNodeRoot.visit([this,&namesToBlock](const StatNode* node_, const StatNode::Stat& self, const StatNode::Stat& children) {
+ bool block = d_smtVisitor(*node_, self, children);
+ if (block) {
+ namesToBlock.insert(DNSName(node_->fullname));
+ }
+ },
+ node);
+
+ if (!namesToBlock.empty()) {
+ updated = false;
+ SuffixMatchTree<DynBlock> smtBlocks = g_dynblockSMT.getCopy();
+ for (const auto& name : namesToBlock) {
+ addOrRefreshBlockSMT(smtBlocks, now, name, d_suffixMatchRule, updated);
+ }
+ if (updated) {
+ g_dynblockSMT.setState(smtBlocks);
+ }
+ }
+ }
}
void excludeRange(const Netmask& range)
d_excludedSubnets.addMask(range, false);
}
+ void excludeDomain(const DNSName& domain)
+ {
+ d_excludedDomains.add(domain);
+ }
+
std::string toString() const
{
std::stringstream result;
result << "Query rate rule: " << d_queryRateRule.toString() << std::endl;
result << "Response rate rule: " << d_respRateRule.toString() << std::endl;
+ result << "SuffixMatch rule: " << d_suffixMatchRule.toString() << std::endl;
result << "RCode rules: " << std::endl;
for (const auto& rule : d_rcodeRules) {
result << "- " << RCode::to_s(rule.first) << ": " << rule.second.toString() << std::endl;
result << "- " << QType(rule.first).getName() << ": " << rule.second.toString() << std::endl;
}
result << "Excluded Subnets: " << d_excludedSubnets.toString() << std::endl;
+ result << "Excluded Domains: " << d_excludedDomains.toString() << std::endl;
return result.str();
}
updated = true;
}
+ void addOrRefreshBlockSMT(SuffixMatchTree<DynBlock>& blocks, const struct timespec& now, const DNSName& name, const DynBlockRule& rule, bool& updated)
+ {
+ if (d_excludedDomains.check(name)) {
+ /* do not add a block for excluded domains */
+ return;
+ }
+
+ struct timespec until = now;
+ until.tv_sec += rule.d_blockDuration;
+ unsigned int count = 0;
+ const auto& got = blocks.lookup(name);
+ bool expired = false;
+
+ if (got) {
+ if (until < got->until) {
+ // had a longer policy
+ return;
+ }
+
+ if (now < got->until) {
+ // only inherit count on fresh query we are extending
+ count = got->blocks;
+ }
+ else {
+ expired = true;
+ }
+ }
+
+ DynBlock db{rule.d_blockReason, until, name, rule.d_action};
+ db.blocks = count;
+
+ if (!d_beQuiet && (!got || expired)) {
+ warnlog("Inserting dynamic block for %s for %d seconds: %s", name, rule.d_blockDuration, rule.d_blockReason);
+ }
+ blocks.add(name, db);
+ updated = true;
+ }
+
void addBlock(boost::optional<NetmaskTree<DynBlock> >& blocks, const struct timespec& now, const ComboAddress& requestor, const DynBlockRule& rule, bool& updated)
{
addOrRefreshBlock(blocks, now, requestor, rule, updated, false);
return d_respRateRule.isEnabled() || !d_rcodeRules.empty();
}
+ bool hasSuffixMatchRules() const
+ {
+ return d_suffixMatchRule.isEnabled();
+ }
+
bool hasRules() const
{
return hasQueryRules() || hasResponseRules();
}
}
- void processResponseRules(counts_t& counts, const struct timespec& now)
+ void processResponseRules(counts_t& counts, StatNode& root, const struct timespec& now)
{
- if (!hasResponseRules()) {
+ if (!hasResponseRules() && !hasSuffixMatchRules()) {
return;
}
d_respRateRule.d_cutOff = d_respRateRule.d_minTime = now;
d_respRateRule.d_cutOff.tv_sec -= d_respRateRule.d_seconds;
+ d_suffixMatchRule.d_cutOff = d_suffixMatchRule.d_minTime = now;
+ d_suffixMatchRule.d_cutOff.tv_sec -= d_suffixMatchRule.d_seconds;
+
for (auto& rule : d_rcodeRules) {
rule.second.d_cutOff = rule.second.d_minTime = now;
rule.second.d_cutOff.tv_sec -= rule.second.d_seconds;
}
bool respRateMatches = d_respRateRule.matches(c.when);
+ bool suffixMatchRuleMatches = d_suffixMatchRule.matches(c.when);
bool rcodeRuleMatches = checkIfResponseCodeMatches(c);
if (respRateMatches || rcodeRuleMatches) {
entry.d_rcodeCounts[c.dh.rcode]++;
}
}
+
+ if (suffixMatchRuleMatches) {
+ root.submit(c.name, c.dh.rcode, boost::none);
+ }
}
}
}
std::map<uint16_t, DynBlockRule> d_qtypeRules;
DynBlockRule d_queryRateRule;
DynBlockRule d_respRateRule;
+ DynBlockRule d_suffixMatchRule;
NetmaskGroup d_excludedSubnets;
+ SuffixMatchNode d_excludedDomains;
+ smtVisitor_t d_smtVisitor;
bool d_beQuiet{false};
};
group->setResponseByteRate(rate, warningRate ? *warningRate : 0, seconds, reason, blockDuration, action ? *action : DNSAction::Action::None);
}
});
+ g_lua.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(unsigned int, const std::string&, unsigned int, boost::optional<DNSAction::Action>, DynBlockRulesGroup::smtVisitor_t)>("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) {
+ if (group) {
+ group->setSuffixMatchRule(seconds, reason, blockDuration, action ? *action : DNSAction::Action::None, visitor);
+ }
+ });
g_lua.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>)>("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) {
if (group) {
group->setRCodeRate(rcode, rate, warningRate ? *warningRate : 0, seconds, reason, blockDuration, action ? *action : DNSAction::Action::None);
group->includeRange(Netmask(*boost::get<std::string>(&ranges)));
}
});
+ g_lua.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(boost::variant<std::string, std::vector<std::pair<int, std::string>>>)>("excludeDomains", [](std::shared_ptr<DynBlockRulesGroup>& group, boost::variant<std::string, std::vector<std::pair<int, std::string>>> domains) {
+ if (domains.type() == typeid(std::vector<std::pair<int, std::string>>)) {
+ for (const auto& range : *boost::get<std::vector<std::pair<int, std::string>>>(&domains)) {
+ group->excludeDomain(DNSName(range.second));
+ }
+ }
+ else {
+ group->excludeDomain(DNSName(*boost::get<std::string>(&domains)));
+ }
+ });
g_lua.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)()>("apply", [](std::shared_ptr<DynBlockRulesGroup>& group) {
group->apply();
});