return d_enabled;
}
+ std::string toString() const
+ {
+ if (!isEnabled()) {
+ return "";
+ }
+
+ std::stringstream result;
+ if (d_action != DNSAction::Action::None) {
+ result << DNSAction::typeToString(d_action) << " ";
+ }
+ else {
+ result << "Apply the global DynBlock action ";
+ }
+ result << "for " << std::to_string(d_blockDuration) << " seconds when over " << std::to_string(d_rate) << " during the last " << d_seconds << " seconds, reason: '" << d_blockReason << "'";
+
+ return result.str();
+ }
+
std::string d_blockReason;
struct timespec d_cutOff;
struct timespec d_minTime;
}
}
+ void excludeRange(const Netmask& range)
+ {
+ d_excludedSubnets.addMask(range);
+ }
+
+ void includeRange(const Netmask& range)
+ {
+ d_excludedSubnets.addMask(range, false);
+ }
+
+ 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 << "RCode rules: " << std::endl;
+ for (const auto& rule : d_rcodeRules) {
+ result << "- " << RCode::to_s(rule.first) << ": " << rule.second.toString() << std::endl;
+ }
+ result << "QType rules: " << std::endl;
+ for (const auto& rule : d_qtypeRules) {
+ result << "- " << QType(rule.first).getName() << ": " << rule.second.toString() << std::endl;
+ }
+ result << "Excluded Subnets: " << d_excludedSubnets.toString() << std::endl;
+
+ return result.str();
+ }
+
private:
bool checkIfQueryTypeMatches(const Rings::Query& query)
{
void addBlock(boost::optional<NetmaskTree<DynBlock> >& blocks, const struct timespec& now, const ComboAddress& requestor, const DynBlockRule& rule, bool& updated)
{
+ if (d_excludedSubnets.match(requestor)) {
+ /* do not add a block for excluded subnets */
+ return;
+ }
+
if (!blocks) {
blocks = g_dynblockNMG.getCopy();
}
std::map<uint16_t, DynBlockRule> d_qtypeRules;
DynBlockRule d_queryRateRule;
DynBlockRule d_respRateRule;
+ NetmaskGroup d_excludedSubnets;
};
group->setQTypeRate(qtype, rate, 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) {
+ if (ranges.type() == typeid(std::vector<std::pair<int, std::string>>)) {
+ for (const auto& range : *boost::get<std::vector<std::pair<int, std::string>>>(&ranges)) {
+ group->excludeRange(Netmask(range.second));
+ }
+ }
+ else {
+ group->excludeRange(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>>>)>("includeRange", [](std::shared_ptr<DynBlockRulesGroup>& group, boost::variant<std::string, std::vector<std::pair<int, std::string>>> ranges) {
+ if (ranges.type() == typeid(std::vector<std::pair<int, std::string>>)) {
+ for (const auto& range : *boost::get<std::vector<std::pair<int, std::string>>>(&ranges)) {
+ group->includeRange(Netmask(range.second));
+ }
+ }
+ else {
+ group->includeRange(Netmask(*boost::get<std::string>(&ranges)));
+ }
+ });
g_lua.registerFunction("apply", &DynBlockRulesGroup::apply);
+ g_lua.registerFunction("toString", &DynBlockRulesGroup::toString);
}
{
public:
enum class Action { Drop, Nxdomain, Refused, Spoof, Allow, HeaderModify, Pool, Delay, Truncate, ServFail, None};
+ static std::string typeToString(const Action& action)
+ {
+ switch(action) {
+ case Action::Drop:
+ return "Drop";
+ case Action::Nxdomain:
+ return "Send NXDomain";
+ case Action::Refused:
+ return "Send Refused";
+ case Action::Spoof:
+ return "Spoof an answer";
+ case Action::Allow:
+ return "Allow";
+ case Action::HeaderModify:
+ return "Modify the header";
+ case Action::Pool:
+ return "Route to a pool";
+ case Action::Delay:
+ return "Delay";
+ case Action::Truncate:
+ return "Truncate over UDP";
+ case Action::ServFail:
+ return "Send ServFail";
+ case Action::None:
+ return "Do nothing";
+ }
+
+ return "Unknown";
+ }
+
virtual Action operator()(DNSQuestion*, string* ruleresult) const =0;
virtual ~DNSAction()
{
The old syntax would walk the query buffer 2 times and the response one 3 times, while the new syntax does it only once for each.
It also reuse the same internal table to keep track of the source IPs, reducing the CPU usage.
+
+DynBlockRulesGroup also offers the ability to specify that some network ranges should be excluded from dynamic blocking:
+
+.. code-block:: lua
+
+ -- do not add dynamic blocks for hosts in the 192.0.2.0/24 and 2001:db8::/32 ranges
+ dbr:excludeRange({"192.0.2.0/24", "2001:db8::/32" })
+ -- except for 192.0.2.1
+ dbr:includeRange("192.0.2.1/32")
+
Walk the in-memory query and response ring buffers and apply the configured rate-limiting rules, adding dynamic blocks when the limits have been exceeded.
+ .. method:: DynBlockRulesGroup:excludeRange(netmasks)
+
+ .. versionadded:: 1.3.1
+
+ Exclude this range, or list of ranges, meaning that no dynamic block will ever be inserted for clients in that range. Default to empty, meaning rules are applied to all ranges. When used in combination with :meth:`DynBlockRulesGroup:includeRange`, the more specific entry wins.
+
+ :param int netmasks: A netmask, or list of netmasks, as strings, like for example "192.0.2.1/24"
+
+ .. method:: DynBlockRulesGroup:includeRange(netmasks)
+
+ .. versionadded:: 1.3.1
+
+ Include this range, or list of ranges, meaning that rules will be applied to this range. When used in combination with :meth:`DynBlockRulesGroup:excludeRange`, the more specific entry wins.
+
+ :param int netmasks: A netmask, or list of netmasks, as strings, like for example "192.0.2.1/24"
+
+ .. method:: DynBlockRulesGroup:toString()
+
+ .. versionadded:: 1.3.1
+
+ Return a string describing the rules and range exclusions of this DynBlockRulesGroup.
+
Other functions
---------------
"""
name = 'responsebyterate.group.dynblocks.tests.powerdns.com.'
self.doTestResponseByteRate(name)
+
+class TestDynBlockGroupResponseBytes(DynBlocksTest):
+
+ _dynBlockQPS = 10
+ _dynBlockPeriod = 2
+ _dynBlockDuration = 5
+ _consoleKey = DNSDistTest.generateConsoleKey()
+ _consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')
+ _config_params = ['_consoleKeyB64', '_consolePort', '_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
+ _config_template = """
+ setKey("%s")
+ controlSocket("127.0.0.1:%s")
+ local dbr = dynBlockRulesGroup()
+ dbr:setQueryRate(%d, %d, "Exceeded query rate", %d)
+ dbr:excludeRange("127.0.0.1/32")
+
+ function maintenance()
+ dbr:apply()
+ end
+
+ newServer{address="127.0.0.1:%s"}
+ """
+
+ def testExcluded(self):
+ """
+ Dyn Blocks (group) : Excluded from the dynamic block rules
+ """
+ name = 'excluded.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._dynBlockQPS * 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()
+
+ # we should have been blocked
+ self.assertEqual(allowed, sent)
+
+ # wait for the maintenance function to run
+ time.sleep(2)
+
+ # we should still not be blocked
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(receivedResponse, receivedResponse)