*/
#pragma once
+#include <unordered_set>
+
#include "dolog.hh"
#include "dnsdist-rings.hh"
+#include "statnode.hh"
+
+#include "dnsdist-lua-inspection-ffi.hh"
+
+// dnsdist_ffi_stat_node_t is a lightuserdata
+template<>
+struct LuaContext::Pusher<dnsdist_ffi_stat_node_t*> {
+ static const int minSize = 1;
+ static const int maxSize = 1;
+
+ static PushedObject push(lua_State* state, dnsdist_ffi_stat_node_t* ptr) noexcept {
+ lua_pushlightuserdata(state, ptr);
+ return PushedObject{state, 1};
+ }
+};
+
+typedef std::function<bool(dnsdist_ffi_stat_node_t*)> dnsdist_ffi_stat_node_visitor_t;
+
+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_)
+ {
+ }
+
+ const StatNode& node;
+ const StatNode::Stat& self;
+ const StatNode::Stat& children;
+};
class DynBlockRulesGroup
{
{
}
- DynBlockRule(const std::string& blockReason, unsigned int blockDuration, unsigned int rate, unsigned int seconds, DNSAction::Action action): d_blockReason(blockReason), d_blockDuration(blockDuration), d_rate(rate), d_seconds(seconds), d_action(action), d_enabled(true)
+ DynBlockRule(const std::string& blockReason, unsigned int blockDuration, unsigned int rate, unsigned int warningRate, unsigned int seconds, DNSAction::Action action): d_blockReason(blockReason), d_blockDuration(blockDuration), d_rate(rate), d_warningRate(warningRate), d_seconds(seconds), d_action(action), d_enabled(true)
{
}
return (count > limit);
}
+ bool warningRateExceeded(unsigned int count, const struct timespec& now) const
+ {
+ if (d_warningRate == 0) {
+ return false;
+ }
+
+ double delta = d_seconds ? d_seconds : DiffTime(now, d_minTime);
+ double limit = delta * d_warningRate;
+ return (count > limit);
+ }
+
bool isEnabled() const
{
return d_enabled;
struct timespec d_minTime;
unsigned int d_blockDuration{0};
unsigned int d_rate{0};
+ unsigned int d_warningRate{0};
unsigned int d_seconds{0};
DNSAction::Action d_action{DNSAction::Action::None};
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()
{
}
- void setQueryRate(unsigned int rate, unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action)
+ void setQueryRate(unsigned int rate, unsigned int warningRate, unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action)
{
- d_queryRateRule = DynBlockRule(reason, blockDuration, rate, seconds, action);
+ d_queryRateRule = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action);
}
- void setResponseByteRate(unsigned int rate, unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action)
+ /* rate is in bytes per second */
+ void setResponseByteRate(unsigned int rate, unsigned int warningRate, unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action)
{
- d_respRateRule = DynBlockRule(reason, blockDuration, rate, seconds, action);
+ d_respRateRule = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action);
}
- void setRCodeRate(uint8_t rcode, unsigned int rate, unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action)
+ void setRCodeRate(uint8_t rcode, unsigned int rate, unsigned int warningRate, unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action)
{
auto& entry = d_rcodeRules[rcode];
- entry = DynBlockRule(reason, blockDuration, rate, seconds, action);
+ entry = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action);
}
- void setQTypeRate(uint16_t qtype, unsigned int rate, unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action)
+ void setQTypeRate(uint16_t qtype, unsigned int rate, unsigned int warningRate, unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action)
{
auto& entry = d_qtypeRules[qtype];
- entry = DynBlockRule(reason, blockDuration, rate, seconds, action);
+ 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 setSuffixMatchRuleFFI(unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action, dnsdist_ffi_stat_node_visitor_t visitor)
+ {
+ d_suffixMatchRule = DynBlockRule(reason, blockDuration, 0, 0, seconds, action);
+ d_smtVisitorFFI = visitor;
}
void apply()
+ {
+ struct timespec now;
+ gettime(&now);
+
+ apply(now);
+ }
+
+ void apply(const struct timespec& now)
{
counts_t counts;
+ StatNode statNodeRoot;
size_t entriesCount = 0;
if (hasQueryRules()) {
}
counts.reserve(entriesCount);
- processQueryRules(counts);
- processResponseRules(counts);
+ processQueryRules(counts, now);
+ processResponseRules(counts, statNodeRoot, now);
- if (counts.empty()) {
+ if (counts.empty() && statNodeRoot.empty()) {
return;
}
boost::optional<NetmaskTree<DynBlock> > blocks;
bool updated = false;
- struct timespec now;
- gettime(&now);
for (const auto& entry : counts) {
- if (d_queryRateRule.rateExceeded(entry.second.queries, now)) {
- addBlock(blocks, now, entry.first, d_queryRateRule, updated);
+ const auto& requestor = entry.first;
+ const auto& counters = entry.second;
+
+ if (d_queryRateRule.warningRateExceeded(counters.queries, now)) {
+ handleWarning(blocks, now, requestor, d_queryRateRule, updated);
+ }
+
+ if (d_queryRateRule.rateExceeded(counters.queries, now)) {
+ addBlock(blocks, now, requestor, d_queryRateRule, updated);
continue;
}
- if (d_respRateRule.rateExceeded(entry.second.respBytes, now)) {
- addBlock(blocks, now, entry.first, d_respRateRule, updated);
+ if (d_respRateRule.warningRateExceeded(counters.respBytes, now)) {
+ handleWarning(blocks, now, requestor, d_respRateRule, updated);
+ }
+
+ if (d_respRateRule.rateExceeded(counters.respBytes, now)) {
+ addBlock(blocks, now, requestor, d_respRateRule, updated);
continue;
}
- for (const auto& rule : d_qtypeRules) {
- const auto& typeIt = entry.second.d_qtypeCounts.find(rule.first);
- if (typeIt != entry.second.d_qtypeCounts.cend() && rule.second.rateExceeded(typeIt->second, now)) {
- addBlock(blocks, now, entry.first, rule.second, updated);
- break;
+ for (const auto& pair : d_qtypeRules) {
+ const auto qtype = pair.first;
+
+ const auto& typeIt = counters.d_qtypeCounts.find(qtype);
+ if (typeIt != counters.d_qtypeCounts.cend()) {
+
+ if (pair.second.warningRateExceeded(typeIt->second, now)) {
+ handleWarning(blocks, now, requestor, pair.second, updated);
+ }
+
+ if (pair.second.rateExceeded(typeIt->second, now)) {
+ addBlock(blocks, now, requestor, pair.second, updated);
+ break;
+ }
}
}
- for (const auto& rule : d_rcodeRules) {
- const auto& rcodeIt = entry.second.d_rcodeCounts.find(rule.first);
- if (rcodeIt != entry.second.d_rcodeCounts.cend() && rule.second.rateExceeded(rcodeIt->second, now)) {
- addBlock(blocks, now, entry.first, rule.second, updated);
- break;
+ for (const auto& pair : d_rcodeRules) {
+ const auto rcode = pair.first;
+
+ const auto& rcodeIt = counters.d_rcodeCounts.find(rcode);
+ if (rcodeIt != counters.d_rcodeCounts.cend()) {
+ if (pair.second.warningRateExceeded(rcodeIt->second, now)) {
+ handleWarning(blocks, now, requestor, pair.second, updated);
+ }
+
+ if (pair.second.rateExceeded(rcodeIt->second, now)) {
+ addBlock(blocks, now, requestor, pair.second, updated);
+ break;
+ }
}
}
}
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 = false;
+
+ if (d_smtVisitorFFI) {
+ dnsdist_ffi_stat_node_t tmp(*node_, self, children);
+ block = d_smtVisitorFFI(&tmp);
+ }
+ else {
+ 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();
}
+ void setQuiet(bool quiet)
+ {
+ d_beQuiet = quiet;
+ }
+
private:
bool checkIfQueryTypeMatches(const Rings::Query& query)
{
return rule->second.matches(response.when);
}
- void addBlock(boost::optional<NetmaskTree<DynBlock> >& blocks, const struct timespec& now, const ComboAddress& requestor, const DynBlockRule& rule, bool& updated)
+ void addOrRefreshBlock(boost::optional<NetmaskTree<DynBlock> >& blocks, const struct timespec& now, const ComboAddress& requestor, const DynBlockRule& rule, bool& updated, bool warning)
{
if (d_excludedSubnets.match(requestor)) {
/* do not add a block for excluded subnets */
unsigned int count = 0;
const auto& got = blocks->lookup(Netmask(requestor));
bool expired = false;
+ bool wasWarning = false;
+
if (got) {
- if (until < got->second.until) {
- // had a longer policy
+ if (warning && !got->second.warning) {
+ /* we have an existing entry which is not a warning,
+ don't override it */
return;
}
+ else if (!warning && got->second.warning) {
+ wasWarning = true;
+ }
+ else {
+ if (until < got->second.until) {
+ // had a longer policy
+ return;
+ }
+ }
if (now < got->second.until) {
// only inherit count on fresh query we are extending
}
}
- DynBlock db{rule.d_blockReason, until, DNSName(), rule.d_action};
+ DynBlock db{rule.d_blockReason, until, DNSName(), warning ? DNSAction::Action::NoOp : rule.d_action};
db.blocks = count;
- if (!got || expired) {
- warnlog("Inserting dynamic block for %s for %d seconds: %s", requestor.toString(), rule.d_blockDuration, rule.d_blockReason);
+ db.warning = warning;
+ if (!d_beQuiet && (!got || expired || wasWarning)) {
+ warnlog("Inserting %sdynamic block for %s for %d seconds: %s", warning ? "(warning) " :"", requestor.toString(), rule.d_blockDuration, rule.d_blockReason);
}
blocks->insert(Netmask(requestor)).second = db;
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);
+ }
+
+ void handleWarning(boost::optional<NetmaskTree<DynBlock> >& blocks, const struct timespec& now, const ComboAddress& requestor, const DynBlockRule& rule, bool& updated)
+ {
+ addOrRefreshBlock(blocks, now, requestor, rule, updated, true);
+ }
+
bool hasQueryRules() const
{
return d_queryRateRule.isEnabled() || !d_qtypeRules.empty();
return d_respRateRule.isEnabled() || !d_rcodeRules.empty();
}
+ bool hasSuffixMatchRules() const
+ {
+ return d_suffixMatchRule.isEnabled();
+ }
+
bool hasRules() const
{
return hasQueryRules() || hasResponseRules();
}
- void processQueryRules(counts_t& counts)
+ void processQueryRules(counts_t& counts, const struct timespec& now)
{
if (!hasQueryRules()) {
return;
}
- struct timespec now;
- gettime(&now);
d_queryRateRule.d_cutOff = d_queryRateRule.d_minTime = now;
d_queryRateRule.d_cutOff.tv_sec -= d_queryRateRule.d_seconds;
}
}
- void processResponseRules(counts_t& counts)
+ void processResponseRules(counts_t& counts, StatNode& root, const struct timespec& now)
{
- if (!hasResponseRules()) {
+ if (!hasResponseRules() && !hasSuffixMatchRules()) {
return;
}
- struct timespec now;
- gettime(&now);
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;
+ dnsdist_ffi_stat_node_visitor_t d_smtVisitorFFI;
+ bool d_beQuiet{false};
};