{
}
- 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)
{
+ if (d_warningRate > 0) {
+ d_warningEnabled = true;
+ }
}
bool matches(const struct timespec& when)
return (count > limit);
}
+ bool warningRateExceeded(unsigned int count, const struct timespec& now) const
+ {
+ if (!d_warningEnabled) {
+ 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;
+ return d_enabled || d_warningEnabled;
}
std::string toString() const
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};
+ bool d_warningEnabled{false};
};
typedef std::unordered_map<ComboAddress, Counts, ComboAddress::addressOnlyHash, ComboAddress::addressOnlyEqual> counts_t;
{
}
- 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);
}
void apply()
+ {
+ struct timespec now;
+ gettime(&now);
+
+ apply(now);
+ }
+
+ void apply(const struct timespec& now)
{
counts_t counts;
}
counts.reserve(entriesCount);
- processQueryRules(counts);
- processResponseRules(counts);
+ processQueryRules(counts, now);
+ processResponseRules(counts, now);
if (counts.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;
+ }
}
}
}
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 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 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, const struct timespec& now)
{
if (!hasResponseRules()) {
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;
DynBlockRule d_queryRateRule;
DynBlockRule d_respRateRule;
NetmaskGroup d_excludedSubnets;
+ bool d_beQuiet{false};
};
/* DynBlockRulesGroup */
g_lua.writeFunction("dynBlockRulesGroup", []() { return std::make_shared<DynBlockRulesGroup>(); });
- g_lua.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(unsigned int, unsigned int, const std::string&, unsigned int, boost::optional<DNSAction::Action>)>("setQueryRate", [](std::shared_ptr<DynBlockRulesGroup>& group, unsigned int rate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, boost::optional<DNSAction::Action> action) {
+ g_lua.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(unsigned int, unsigned int, const std::string&, unsigned int, boost::optional<DNSAction::Action>, boost::optional<unsigned int>)>("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) {
if (group) {
- group->setQueryRate(rate, seconds, reason, blockDuration, action ? *action : DNSAction::Action::None);
+ group->setQueryRate(rate, warningRate ? *warningRate : 0, seconds, reason, blockDuration, action ? *action : DNSAction::Action::None);
}
});
- g_lua.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(unsigned int, unsigned int, const std::string&, unsigned int, boost::optional<DNSAction::Action>)>("setResponseByteRate", [](std::shared_ptr<DynBlockRulesGroup>& group, unsigned int rate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, boost::optional<DNSAction::Action> action) {
+ g_lua.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(unsigned int, unsigned int, const std::string&, unsigned int, boost::optional<DNSAction::Action>, boost::optional<unsigned int>)>("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) {
if (group) {
- group->setResponseByteRate(rate, seconds, reason, blockDuration, action ? *action : DNSAction::Action::None);
+ group->setResponseByteRate(rate, warningRate ? *warningRate : 0, seconds, reason, blockDuration, action ? *action : DNSAction::Action::None);
}
});
- g_lua.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(uint8_t, unsigned int, unsigned int, const std::string&, unsigned int, boost::optional<DNSAction::Action>)>("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) {
+ 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, seconds, reason, blockDuration, action ? *action : DNSAction::Action::None);
+ group->setRCodeRate(rcode, rate, warningRate ? *warningRate : 0, seconds, reason, blockDuration, action ? *action : DNSAction::Action::None);
}
});
- g_lua.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(uint16_t, unsigned int, unsigned int, const std::string&, unsigned int, boost::optional<DNSAction::Action>)>("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) {
+ g_lua.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>)>("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) {
if (group) {
- group->setQTypeRate(qtype, rate, seconds, reason, blockDuration, action ? *action : DNSAction::Action::None);
+ group->setQTypeRate(qtype, rate, warningRate ? *warningRate : 0, seconds, reason, blockDuration, action ? *action : DNSAction::Action::None);
}
});
g_lua.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(boost::variant<std::string, std::vector<std::pair<int, std::string>>>)>("excludeRange", [](std::shared_ptr<DynBlockRulesGroup>& group, boost::variant<std::string, std::vector<std::pair<int, std::string>>> ranges) {
group->includeRange(Netmask(*boost::get<std::string>(&ranges)));
}
});
- g_lua.registerFunction("apply", &DynBlockRulesGroup::apply);
+ g_lua.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)()>("apply", [](std::shared_ptr<DynBlockRulesGroup>& group) {
+ group->apply();
+ });
g_lua.registerFunction("toString", &DynBlockRulesGroup::toString);
}
auto slow = g_dynblockNMG.getCopy();
struct timespec now;
gettime(&now);
- boost::format fmt("%-24s %8d %8d %-20s %s\n");
- g_outputBuffer = (fmt % "What" % "Seconds" % "Blocks" % "Action" % "Reason").str();
+ boost::format fmt("%-24s %8d %8d %-10s %-20s %s\n");
+ g_outputBuffer = (fmt % "What" % "Seconds" % "Blocks" % "Warning" % "Action" % "Reason").str();
for(const auto& e: slow) {
if(now < e->second.until)
- g_outputBuffer+= (fmt % e->first.toString() % (e->second.until.tv_sec - now.tv_sec) % e->second.blocks % DNSAction::typeToString(e->second.action != DNSAction::Action::None ? e->second.action : g_dynBlockAction) % e->second.reason).str();
+ g_outputBuffer+= (fmt % e->first.toString() % (e->second.until.tv_sec - now.tv_sec) % e->second.blocks % (e->second.warning ? "true" : "false") % DNSAction::typeToString(e->second.action != DNSAction::Action::None ? e->second.action : g_dynBlockAction) % e->second.reason).str();
}
auto slow2 = g_dynblockSMT.getCopy();
slow2.visit([&now, &fmt](const SuffixMatchTree<DynBlock>& node) {
string dom("empty");
if(!node.d_value.domain.empty())
dom = node.d_value.domain.toString();
- g_outputBuffer+= (fmt % dom % (node.d_value.until.tv_sec - now.tv_sec) % node.d_value.blocks % DNSAction::typeToString(node.d_value.action != DNSAction::Action::None ? node.d_value.action : g_dynBlockAction) % node.d_value.reason).str();
+ g_outputBuffer+= (fmt % dom % (node.d_value.until.tv_sec - now.tv_sec) % node.d_value.blocks % (node.d_value.warning ? "true" : "false") % DNSAction::typeToString(node.d_value.action != DNSAction::Action::None ? node.d_value.action : g_dynBlockAction) % node.d_value.reason).str();
}
});
insertResponseLocked(shard, when, requestor, name, qtype, usec, size, dh, backend);
}
+ void clear()
+ {
+ for (auto& shard : d_shards) {
+ {
+ std::lock_guard<std::mutex> wl(shard->queryLock);
+ shard->queryRing.clear();
+ }
+ {
+ std::lock_guard<std::mutex> wl(shard->respLock);
+ shard->respRing.clear();
+ }
+ }
+
+ d_nbQueryEntries.store(0);
+ d_nbResponseEntries.store(0);
+ d_currentShardId.store(0);
+ d_blockingQueryInserts.store(0);
+ d_blockingResponseInserts.store(0);
+ d_deferredQueryInserts.store(0);
+ d_deferredResponseInserts.store(0);
+ }
+
std::vector<std::unique_ptr<Shard> > d_shards;
std::atomic<uint64_t> d_blockingQueryInserts;
std::atomic<uint64_t> d_blockingResponseInserts;
{"reason", e->second.reason},
{"seconds", (double)(e->second.until.tv_sec - now.tv_sec)},
{"blocks", (double)e->second.blocks},
- {"action", DNSAction::typeToString(e->second.action != DNSAction::Action::None ? e->second.action : g_dynBlockAction) }
+ {"action", DNSAction::typeToString(e->second.action != DNSAction::Action::None ? e->second.action : g_dynBlockAction) },
+ {"warning", e->second.warning }
};
obj.insert({e->first.toString(), thing});
}
struct DynBlock
{
- DynBlock(): action(DNSAction::Action::None)
+ DynBlock(): action(DNSAction::Action::None), warning(false)
{
}
- DynBlock(const std::string& reason_, const struct timespec& until_, const DNSName& domain_, DNSAction::Action action_): reason(reason_), until(until_), domain(domain_), action(action_)
+ DynBlock(const std::string& reason_, const struct timespec& until_, const DNSName& domain_, DNSAction::Action action_): reason(reason_), until(until_), domain(domain_), action(action_), warning(false)
{
}
- DynBlock(const DynBlock& rhs): reason(rhs.reason), until(rhs.until), domain(rhs.domain), action(rhs.action)
+ DynBlock(const DynBlock& rhs): reason(rhs.reason), until(rhs.until), domain(rhs.domain), action(rhs.action), warning(rhs.warning)
{
blocks.store(rhs.blocks);
}
domain=rhs.domain;
action=rhs.action;
blocks.store(rhs.blocks);
+ warning=rhs.warning;
return *this;
}
DNSName domain;
DNSAction::Action action;
mutable std::atomic<unsigned int> blocks;
+ bool warning;
};
extern GlobalStateHolder<NetmaskTree<DynBlock>> g_dynblockNMG;
test-base64_cc.cc \
test-dnscrypt_cc.cc \
test-dnsdist_cc.cc \
+ test-dnsdistdynblocks_hh.cc \
test-dnsdistpacketcache_cc.cc \
test-dnsdistrings_cc.cc \
test-dnsdistrules_cc.cc \
DynBlockRulesGroup
------------------
-Starting with dnsdist 1.3.0, a new `:ref:dynBlockRulesGroup` function can be used to return a `DynBlockRulesGroup` instance,
+Starting with dnsdist 1.3.0, a new :ref:`dynBlockRulesGroup` function can be used to return a `DynBlockRulesGroup` instance,
designed to make the processing of multiple rate-limiting rules faster by walking the query and response buffers only once
for each invocation, instead of once per existing `exceed*()` invocation.
-- except for 192.0.2.1
dbr:includeRange("192.0.2.1/32")
+
+Since 1.3.3, it's also possible to define a warning rate. When the query or response rate raises above the warning level but below
+the trigger level, a warning message will be issued along with a no-op block. If the rate reaches the trigger level, the regular
+action is applied.
+
+.. code-block:: lua
+
+ local dbr = dynBlockRulesGroup()
+ -- generate a warning above 100 qps for 10s, and start dropping incoming queries above 300 qps for 10s
+ dbr:setQueryRate(300, 10, "Exceeded query rate", 60, 100)
+
Represents a group of dynamic block rules.
- .. method:: DynBlockRulesGroup:setQueryRate(rate, seconds, reason, blockingTime [, action])
+ .. method:: DynBlockRulesGroup:setQueryRate(rate, seconds, reason, blockingTime [, action [, warningRate]])
+
+ .. versionchanged:: 1.3.3
+ ``warningRate`` parameter added.
Adds a query rate-limiting rule, equivalent to:
```
:param string reason: The message to show next to the blocks
:param int blockingTime: The number of seconds this block to expire
:param int action: The action to take when the dynamic block matches, see :ref:`here <DNSAction>`. (default to the one set with :func:`setDynBlocksAction`)
+ :param int warningRate: If set to a non-zero value, the rate above which a warning message will be issued and a no-op block inserted
+
+ .. method:: DynBlockRulesGroup:setRCodeRate(rcode, rate, seconds, reason, blockingTime [, action [, warningRate]])
- .. method:: DynBlockRulesGroup:setRCodeRate(rcode, rate, seconds, reason, blockingTime [, action])
+ .. versionchanged:: 1.3.3
+ ``warningRate`` parameter added.
Adds a rate-limiting rule for responses of code ``rcode``, equivalent to:
```
:param string reason: The message to show next to the blocks
:param int blockingTime: The number of seconds this block to expire
:param int action: The action to take when the dynamic block matches, see :ref:`here <DNSAction>`. (default to the one set with :func:`setDynBlocksAction`)
+ :param int warningRate: If set to a non-zero value, the rate above which a warning message will be issued and a no-op block inserted
- .. method:: DynBlockRulesGroup:setQTypeRate(qtype, rate, seconds, reason, blockingTime [, action])
+ .. method:: DynBlockRulesGroup:setQTypeRate(qtype, rate, seconds, reason, blockingTime [, action [, warningRate]])
+
+ .. versionchanged:: 1.3.3
+ ``warningRate`` parameter added.
Adds a rate-limiting rule for queries of type ``qtype``, equivalent to:
```
:param string reason: The message to show next to the blocks
:param int blockingTime: The number of seconds this block to expire
:param int action: The action to take when the dynamic block matches, see :ref:`here <DNSAction>`. (default to the one set with :func:`setDynBlocksAction`)
+ :param int warningRate: If set to a non-zero value, the rate above which a warning message will be issued and a no-op block inserted
+
+ .. method:: DynBlockRulesGroup:setResponseByteRate(rate, seconds, reason, blockingTime [, action [, warningRate]])
- .. method:: DynBlockRulesGroup:setRespByteRate(rate, seconds, reason, blockingTime [, action])
+ .. versionchanged:: 1.3.3
+ ``warningRate`` parameter added.
Adds a bandwidth rate-limiting rule for responses, equivalent to:
```
:param string reason: The message to show next to the blocks
:param int blockingTime: The number of seconds this block to expire
:param int action: The action to take when the dynamic block matches, see :ref:`here <DNSAction>`. (default to the one set with :func:`setDynBlocksAction`)
+ :param int warningRate: If set to a non-zero value, the rate above which a warning message will be issued and a no-op block inserted
.. method:: DynBlockRulesGroup:apply()
--- /dev/null
+
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_NO_MAIN
+
+#include <boost/test/unit_test.hpp>
+
+#include "dnsdist.hh"
+#include "dnsdist-dynblocks.hh"
+#include "dnsdist-rings.hh"
+
+Rings g_rings;
+GlobalStateHolder<NetmaskTree<DynBlock>> g_dynblockNMG;
+
+BOOST_AUTO_TEST_SUITE(dnsdistdynblocks_hh)
+
+BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_QueryRate) {
+ dnsheader dh;
+ DNSName qname("rings.powerdns.com.");
+ ComboAddress requestor1("192.0.2.1");
+ ComboAddress requestor2("192.0.2.2");
+ uint16_t qtype = QType::AAAA;
+ uint16_t size = 42;
+ struct timespec now;
+ gettime(&now);
+ NetmaskTree<DynBlock> emptyNMG;
+
+ size_t numberOfSeconds = 10;
+ size_t blockDuration = 60;
+ const auto action = DNSAction::Action::Drop;
+ const std::string reason = "Exceeded query rate";
+
+ DynBlockRulesGroup dbrg;
+ dbrg.setQuiet(true);
+
+ /* block above 50 qps for numberOfSeconds seconds, no warning */
+ dbrg.setQueryRate(50, 0, numberOfSeconds, reason, blockDuration, action);
+
+ {
+ /* insert 45 qps from a given client in the last 10s
+ this should not trigger the rule */
+ size_t numberOfQueries = 45 * numberOfSeconds;
+ g_rings.clear();
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), 0);
+ g_dynblockNMG.setState(emptyNMG);
+
+ for (size_t idx = 0; idx < numberOfQueries; idx++) {
+ g_rings.insertQuery(now, requestor1, qname, qtype, size, dh);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
+
+ dbrg.apply();
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 0);
+ BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor1) == nullptr);
+ }
+
+ {
+ /* insert just above 50 qps from a given client in the last 10s
+ this should trigger the rule this time */
+ size_t numberOfQueries = 50 * numberOfSeconds + 1;
+ g_rings.clear();
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), 0);
+ g_dynblockNMG.setState(emptyNMG);
+
+ for (size_t idx = 0; idx < numberOfQueries; idx++) {
+ g_rings.insertQuery(now, requestor1, qname, qtype, size, dh);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
+
+ dbrg.apply();
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 1);
+ BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor1) != nullptr);
+ BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor2) == nullptr);
+ const auto& block = g_dynblockNMG.getLocal()->lookup(requestor1)->second;
+ BOOST_CHECK_EQUAL(block.reason, reason);
+ BOOST_CHECK_EQUAL(block.until.tv_sec, now.tv_sec + blockDuration);
+ BOOST_CHECK(block.domain.empty());
+ BOOST_CHECK(block.action == action);
+ BOOST_CHECK_EQUAL(block.blocks, 0);
+ BOOST_CHECK_EQUAL(block.warning, false);
+ }
+
+}
+
+BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_QTypeRate) {
+ dnsheader dh;
+ DNSName qname("rings.powerdns.com.");
+ ComboAddress requestor1("192.0.2.1");
+ ComboAddress requestor2("192.0.2.2");
+ uint16_t qtype = QType::AAAA;
+ uint16_t size = 42;
+ struct timespec now;
+ gettime(&now);
+ NetmaskTree<DynBlock> emptyNMG;
+
+ size_t numberOfSeconds = 10;
+ size_t blockDuration = 60;
+ const auto action = DNSAction::Action::Drop;
+ const std::string reason = "Exceeded query rate";
+
+ DynBlockRulesGroup dbrg;
+ dbrg.setQuiet(true);
+
+ /* block above 50 qps for numberOfSeconds seconds, no warning */
+ dbrg.setQTypeRate(QType::AAAA, 50, 0, numberOfSeconds, reason, blockDuration, action);
+
+ {
+ /* insert 45 qps from a given client in the last 10s
+ this should not trigger the rule */
+ size_t numberOfQueries = 45 * numberOfSeconds;
+ g_rings.clear();
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), 0);
+ g_dynblockNMG.setState(emptyNMG);
+
+ for (size_t idx = 0; idx < numberOfQueries; idx++) {
+ g_rings.insertQuery(now, requestor1, qname, qtype, size, dh);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
+
+ dbrg.apply();
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 0);
+ BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor1) == nullptr);
+ }
+
+ {
+ /* insert just above 50 qps from a given client in the last 10s
+ but for the wrong QType */
+ size_t numberOfQueries = 50 * numberOfSeconds + 1;
+ g_rings.clear();
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), 0);
+ g_dynblockNMG.setState(emptyNMG);
+
+ for (size_t idx = 0; idx < numberOfQueries; idx++) {
+ g_rings.insertQuery(now, requestor1, qname, QType::A, size, dh);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
+
+ dbrg.apply();
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 0);
+ BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor1) == nullptr);
+ }
+
+ {
+ // insert just above 50 qps from a given client in the last 10s
+ // this should trigger the rule this time
+ size_t numberOfQueries = 50 * numberOfSeconds + 1;
+ g_rings.clear();
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), 0);
+ g_dynblockNMG.setState(emptyNMG);
+
+ for (size_t idx = 0; idx < numberOfQueries; idx++) {
+ g_rings.insertQuery(now, requestor1, qname, qtype, size, dh);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
+
+ dbrg.apply();
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 1);
+ BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor1) != nullptr);
+ BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor2) == nullptr);
+ const auto& block = g_dynblockNMG.getLocal()->lookup(requestor1)->second;
+ BOOST_CHECK_EQUAL(block.reason, reason);
+ BOOST_CHECK_EQUAL(block.until.tv_sec, now.tv_sec + blockDuration);
+ BOOST_CHECK(block.domain.empty());
+ BOOST_CHECK(block.action == action);
+ BOOST_CHECK_EQUAL(block.blocks, 0);
+ BOOST_CHECK_EQUAL(block.warning, false);
+ }
+
+}
+
+BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_RCodeRate) {
+ dnsheader dh;
+ DNSName qname("rings.powerdns.com.");
+ ComboAddress requestor1("192.0.2.1");
+ ComboAddress requestor2("192.0.2.2");
+ ComboAddress backend("192.0.2.42");
+ uint16_t qtype = QType::AAAA;
+ uint16_t size = 42;
+ unsigned int responseTime = 100 * 1000; /* 100ms */
+ struct timespec now;
+ gettime(&now);
+ NetmaskTree<DynBlock> emptyNMG;
+
+ size_t numberOfSeconds = 10;
+ size_t blockDuration = 60;
+ const auto action = DNSAction::Action::Drop;
+ const std::string reason = "Exceeded query rate";
+ const uint16_t rcode = RCode::ServFail;
+
+ DynBlockRulesGroup dbrg;
+ dbrg.setQuiet(true);
+
+ /* block above 50 ServFail/s for numberOfSeconds seconds, no warning */
+ dbrg.setRCodeRate(rcode, 50, 0, numberOfSeconds, reason, blockDuration, action);
+
+ {
+ /* insert 45 ServFail/s from a given client in the last 10s
+ this should not trigger the rule */
+ size_t numberOfResponses = 45 * numberOfSeconds;
+ g_rings.clear();
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 0);
+ g_dynblockNMG.setState(emptyNMG);
+
+ dh.rcode = rcode;
+ for (size_t idx = 0; idx < numberOfResponses; idx++) {
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), numberOfResponses);
+
+ dbrg.apply();
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 0);
+ BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor1) == nullptr);
+ }
+
+ {
+ /* insert just above 50 FormErr/s from a given client in the last 10s */
+ size_t numberOfResponses = 50 * numberOfSeconds + 1;
+ g_rings.clear();
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 0);
+ g_dynblockNMG.setState(emptyNMG);
+
+ dh.rcode = RCode::FormErr;
+ for (size_t idx = 0; idx < numberOfResponses; idx++) {
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), numberOfResponses);
+
+ dbrg.apply();
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 0);
+ BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor1) == nullptr);
+ }
+
+ {
+ /* insert just above 50 ServFail/s from a given client in the last 10s
+ this should trigger the rule this time */
+ size_t numberOfResponses = 50 * numberOfSeconds + 1;
+ g_rings.clear();
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 0);
+ g_dynblockNMG.setState(emptyNMG);
+
+ dh.rcode = rcode;
+ for (size_t idx = 0; idx < numberOfResponses; idx++) {
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), numberOfResponses);
+
+ dbrg.apply();
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 1);
+ BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor1) != nullptr);
+ BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor2) == nullptr);
+ const auto& block = g_dynblockNMG.getLocal()->lookup(requestor1)->second;
+ BOOST_CHECK_EQUAL(block.reason, reason);
+ BOOST_CHECK_EQUAL(block.until.tv_sec, now.tv_sec + blockDuration);
+ BOOST_CHECK(block.domain.empty());
+ BOOST_CHECK(block.action == action);
+ BOOST_CHECK_EQUAL(block.blocks, 0);
+ BOOST_CHECK_EQUAL(block.warning, false);
+ }
+
+}
+
+BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_ResponseByteRate) {
+ dnsheader dh;
+ DNSName qname("rings.powerdns.com.");
+ ComboAddress requestor1("192.0.2.1");
+ ComboAddress requestor2("192.0.2.2");
+ ComboAddress backend("192.0.2.42");
+ uint16_t qtype = QType::AAAA;
+ uint16_t size = 100;
+ unsigned int responseTime = 100 * 1000; /* 100ms */
+ struct timespec now;
+ gettime(&now);
+ NetmaskTree<DynBlock> emptyNMG;
+
+ size_t numberOfSeconds = 10;
+ size_t blockDuration = 60;
+ const auto action = DNSAction::Action::Drop;
+ const std::string reason = "Exceeded query rate";
+ const uint16_t rcode = RCode::NoError;
+
+ DynBlockRulesGroup dbrg;
+ dbrg.setQuiet(true);
+
+ /* block above 10kB/s for numberOfSeconds seconds, no warning */
+ dbrg.setResponseByteRate(10000, 0, numberOfSeconds, reason, blockDuration, action);
+
+ {
+ /* insert 99 answers of 100 bytes per second from a given client in the last 10s
+ this should not trigger the rule */
+ size_t numberOfResponses = 99 * numberOfSeconds;
+ g_rings.clear();
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 0);
+ g_dynblockNMG.setState(emptyNMG);
+
+ dh.rcode = rcode;
+ for (size_t idx = 0; idx < numberOfResponses; idx++) {
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), numberOfResponses);
+
+ dbrg.apply();
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 0);
+ BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor1) == nullptr);
+ }
+
+ {
+ /* insert just above 100 answers of 100 bytes per second from a given client in the last 10s */
+ size_t numberOfResponses = 100 * numberOfSeconds + 1;
+ g_rings.clear();
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 0);
+ g_dynblockNMG.setState(emptyNMG);
+
+ dh.rcode = rcode;
+ for (size_t idx = 0; idx < numberOfResponses; idx++) {
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), numberOfResponses);
+
+ dbrg.apply();
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 1);
+ BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor1) != nullptr);
+ BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor2) == nullptr);
+ const auto& block = g_dynblockNMG.getLocal()->lookup(requestor1)->second;
+ BOOST_CHECK_EQUAL(block.reason, reason);
+ BOOST_CHECK_EQUAL(block.until.tv_sec, now.tv_sec + blockDuration);
+ BOOST_CHECK(block.domain.empty());
+ BOOST_CHECK(block.action == action);
+ BOOST_CHECK_EQUAL(block.blocks, 0);
+ BOOST_CHECK_EQUAL(block.warning, false);
+ }
+
+}
+
+BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_Warning) {
+ dnsheader dh;
+ DNSName qname("rings.powerdns.com.");
+ ComboAddress requestor1("192.0.2.1");
+ ComboAddress requestor2("192.0.2.2");
+ uint16_t qtype = QType::AAAA;
+ uint16_t size = 42;
+ struct timespec now;
+ gettime(&now);
+ NetmaskTree<DynBlock> emptyNMG;
+
+ size_t numberOfSeconds = 10;
+ size_t blockDuration = 60;
+ const auto action = DNSAction::Action::Drop;
+ const std::string reason = "Exceeded query rate";
+
+ DynBlockRulesGroup dbrg;
+ dbrg.setQuiet(true);
+
+ /* warn above 20 qps for numberOfSeconds seconds, block above 50 qps */
+ dbrg.setQueryRate(50, 20, numberOfSeconds, reason, blockDuration, action);
+
+ {
+ /* insert 20 qps from a given client in the last 10s
+ this should not trigger the rule */
+ size_t numberOfQueries = 20 * numberOfSeconds;
+ g_rings.clear();
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), 0);
+ g_dynblockNMG.setState(emptyNMG);
+
+ for (size_t idx = 0; idx < numberOfQueries; idx++) {
+ g_rings.insertQuery(now, requestor1, qname, qtype, size, dh);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
+
+ dbrg.apply(now);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 0);
+ BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor1) == nullptr);
+ }
+
+ {
+ /* insert just above 20 qps from a given client in the last 10s
+ this should trigger the warning rule this time */
+ size_t numberOfQueries = 20 * numberOfSeconds + 1;
+ g_rings.clear();
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), 0);
+ g_dynblockNMG.setState(emptyNMG);
+
+ for (size_t idx = 0; idx < numberOfQueries; idx++) {
+ g_rings.insertQuery(now, requestor1, qname, qtype, size, dh);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
+
+ dbrg.apply(now);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 1);
+ BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor1) != nullptr);
+ BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor2) == nullptr);
+
+ {
+ const auto& block = g_dynblockNMG.getLocal()->lookup(requestor1)->second;
+ BOOST_CHECK_EQUAL(block.reason, reason);
+ BOOST_CHECK_EQUAL(block.until.tv_sec, now.tv_sec + blockDuration);
+ BOOST_CHECK(block.domain.empty());
+ BOOST_CHECK(block.action == DNSAction::Action::NoOp);
+ BOOST_CHECK_EQUAL(block.blocks, 0);
+ BOOST_CHECK_EQUAL(block.warning, true);
+ /* let's increment the number of blocks so we can check that the counter
+ is preserved when the block is upgraded to a non-warning one */
+ block.blocks++;
+ }
+
+ /* now inserts 50 qps for the same duration, we should reach the blocking threshold */
+ numberOfQueries = 50 * numberOfSeconds + 1;
+ g_rings.clear();
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), 0);
+
+ for (size_t idx = 0; idx < numberOfQueries; idx++) {
+ g_rings.insertQuery(now, requestor1, qname, qtype, size, dh);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
+
+ dbrg.apply(now);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 1);
+ BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor1) != nullptr);
+ BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor2) == nullptr);
+
+ {
+ const auto& block = g_dynblockNMG.getLocal()->lookup(requestor1)->second;
+ BOOST_CHECK_EQUAL(block.reason, reason);
+ BOOST_CHECK_EQUAL(block.until.tv_sec, now.tv_sec + blockDuration);
+ BOOST_CHECK(block.domain.empty());
+ BOOST_CHECK(block.action == action);
+ /* this hsould have been preserved */
+ BOOST_CHECK_EQUAL(block.blocks, 1);
+ BOOST_CHECK_EQUAL(block.warning, false);
+ block.blocks++;
+ }
+
+ /* 30s later, with the same amount of qps the duration of the block
+ should be increased. */
+ now.tv_sec += 30;
+ numberOfQueries = 50 * numberOfSeconds + 1;
+ g_rings.clear();
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), 0);
+
+ for (size_t idx = 0; idx < numberOfQueries; idx++) {
+ g_rings.insertQuery(now, requestor1, qname, qtype, size, dh);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
+
+ dbrg.apply(now);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 1);
+ BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor1) != nullptr);
+ BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor2) == nullptr);
+
+ {
+ const auto& block = g_dynblockNMG.getLocal()->lookup(requestor1)->second;
+ BOOST_CHECK_EQUAL(block.reason, reason);
+ /* should have been updated */
+ BOOST_CHECK_EQUAL(block.until.tv_sec, now.tv_sec + blockDuration);
+ BOOST_CHECK(block.domain.empty());
+ BOOST_CHECK(block.action == action);
+ /* this hsould have been preserved */
+ BOOST_CHECK_EQUAL(block.blocks, 2);
+ BOOST_CHECK_EQUAL(block.warning, false);
+ }
+ }
+
+ {
+ /* insert directly just above 50 qps from a given client in the last 10s
+ this should trigger the blocking rule right away this time */
+ size_t numberOfQueries = 50 * numberOfSeconds + 1;
+ g_rings.clear();
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), 0);
+ g_dynblockNMG.setState(emptyNMG);
+
+ for (size_t idx = 0; idx < numberOfQueries; idx++) {
+ g_rings.insertQuery(now, requestor1, qname, qtype, size, dh);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
+
+ dbrg.apply(now);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 1);
+ BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor1) != nullptr);
+ BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor2) == nullptr);
+
+ {
+ const auto& block = g_dynblockNMG.getLocal()->lookup(requestor1)->second;
+ BOOST_CHECK_EQUAL(block.reason, reason);
+ BOOST_CHECK_EQUAL(block.until.tv_sec, now.tv_sec + blockDuration);
+ BOOST_CHECK(block.domain.empty());
+ BOOST_CHECK(block.action == action);
+ BOOST_CHECK_EQUAL(block.blocks, 0);
+ BOOST_CHECK_EQUAL(block.warning, false);
+ }
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_Ranges) {
+ dnsheader dh;
+ DNSName qname("rings.powerdns.com.");
+ ComboAddress requestor1("192.0.2.1");
+ ComboAddress requestor2("192.0.2.42");
+ uint16_t qtype = QType::AAAA;
+ uint16_t size = 42;
+ struct timespec now;
+ gettime(&now);
+ NetmaskTree<DynBlock> emptyNMG;
+
+ size_t numberOfSeconds = 10;
+ size_t blockDuration = 60;
+ const auto action = DNSAction::Action::Drop;
+ const std::string reason = "Exceeded query rate";
+
+ DynBlockRulesGroup dbrg;
+ dbrg.setQuiet(true);
+ /* include 192.0.2.0 -> 192.0.2.63 */
+ dbrg.includeRange(Netmask("192.0.2.0/26"));
+ /* but exclude 192.0.2.42 only */
+ dbrg.excludeRange(Netmask("192.0.2.42/32"));
+
+ /* block above 50 qps for numberOfSeconds seconds, no warning */
+ dbrg.setQueryRate(50, 0, numberOfSeconds, reason, blockDuration, action);
+
+ {
+ /* insert just above 50 qps from the two clients in the last 10s
+ this should trigger the rule for the first one but not the second one */
+ size_t numberOfQueries = 50 * numberOfSeconds + 1;
+ g_rings.clear();
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), 0);
+ g_dynblockNMG.setState(emptyNMG);
+
+ for (size_t idx = 0; idx < numberOfQueries; idx++) {
+ g_rings.insertQuery(now, requestor1, qname, qtype, size, dh);
+ g_rings.insertQuery(now, requestor2, qname, qtype, size, dh);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries * 2);
+
+ dbrg.apply();
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 1);
+ BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor1) != nullptr);
+ BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor2) == nullptr);
+ const auto& block = g_dynblockNMG.getLocal()->lookup(requestor1)->second;
+ BOOST_CHECK_EQUAL(block.reason, reason);
+ BOOST_CHECK_EQUAL(block.until.tv_sec, now.tv_sec + blockDuration);
+ BOOST_CHECK(block.domain.empty());
+ BOOST_CHECK(block.action == action);
+ BOOST_CHECK_EQUAL(block.blocks, 0);
+ BOOST_CHECK_EQUAL(block.warning, false);
+ }
+
+}
+
+BOOST_AUTO_TEST_SUITE_END()
# check that the rule has been inserted
self.doTestDynBlockViaAPI('127.0.0.1/32', 'Exceeded query rate', self._dynBlockDuration - 4, self._dynBlockDuration, 0, sent)
+
+class TestDynBlockGroupWarning(DynBlocksTest):
+
+ _dynBlockWarningQPS = 5
+ _dynBlockQPS = 20
+ _dynBlockPeriod = 2
+ _dynBlockDuration = 5
+ _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_dynBlockWarningQPS', '_testServerPort', '_webServerPort', '_webServerBasicAuthPassword', '_webServerAPIKey']
+ _config_template = """
+ local dbr = dynBlockRulesGroup()
+ dbr:setQueryRate(%d, %d, "Exceeded query rate", %d, DNSAction.Drop, %d)
+
+ function maintenance()
+ dbr:apply()
+ end
+
+ newServer{address="127.0.0.1:%s"}
+ webserver("127.0.0.1:%s", "%s", "%s")
+ """
+
+ def testWarning(self):
+ """
+ Dyn Blocks (group) : Warning
+ """
+ name = 'warning.group.dynblocks.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '192.0.2.1')
+ response.answer.append(rrset)
+
+ allowed = 0
+ sent = 0
+ for _ in range((self._dynBlockWarningQPS * self._dynBlockPeriod) + 1):
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ sent = sent + 1
+ if receivedQuery:
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(response, receivedResponse)
+ allowed = allowed + 1
+ else:
+ # the query has not reached the responder,
+ # let's clear the response queue
+ self.clearToResponderQueue()
+
+ # a dynamic rule should have been inserted, but the queries should
+ # still go on because we are still at warning level
+ self.assertEqual(allowed, sent)
+
+ # wait for the maintenance function to run
+ time.sleep(2)
+
+ # the rule should still be present, but the queries pass through anyway
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(receivedResponse, receivedResponse)
+
+ # check that the rule has been inserted
+ self.doTestDynBlockViaAPI('127.0.0.1/32', 'Exceeded query rate', self._dynBlockDuration - 4, self._dynBlockDuration, 0, sent)
+
+ self.doTestQRate(name)