bool typeRuleMatches = checkIfQueryTypeMatches(ringEntry);
if (qRateMatches || typeRuleMatches) {
+ if (d_excludedSubnets.match(ringEntry.requestor)) {
+ continue;
+ }
+
auto& entry = counts[AddressAndPortRange(ringEntry.requestor, ringEntry.requestor.isIPv4() ? d_v4Mask : d_v6Mask, d_portMask)];
if (qRateMatches) {
++entry.queries;
continue;
}
+ bool suffixMatchRuleMatches = d_suffixMatchRule.matches(ringEntry.when);
+ if (suffixMatchRuleMatches) {
+ const bool hit = ringEntry.isACacheHit();
+ root.submit(ringEntry.name, ((ringEntry.dh.rcode == 0 && ringEntry.usec == std::numeric_limits<uint32_t>::max()) ? -1 : ringEntry.dh.rcode), ringEntry.size, hit, std::nullopt, g_rings.getSamplingRate());
+ }
+
+ if (d_excludedSubnets.match(ringEntry.requestor)) {
+ continue;
+ }
+
auto& entry = counts[AddressAndPortRange(ringEntry.requestor, ringEntry.requestor.isIPv4() ? d_v4Mask : d_v6Mask, d_portMask)];
++entry.responses;
bool respRateMatches = d_respRateRule.matches(ringEntry.when);
- bool suffixMatchRuleMatches = d_suffixMatchRule.matches(ringEntry.when);
bool rcodeRuleMatches = checkIfResponseCodeMatches(ringEntry);
bool respCacheMissRatioRuleMatches = d_respCacheMissRatioRule.matches(ringEntry.when);
if (respCacheMissRatioRuleMatches && !ringEntry.isACacheHit()) {
++entry.cacheMisses;
}
-
- if (suffixMatchRuleMatches) {
- const bool hit = ringEntry.isACacheHit();
- root.submit(ringEntry.name, ((ringEntry.dh.rcode == 0 && ringEntry.usec == std::numeric_limits<uint32_t>::max()) ? -1 : ringEntry.dh.rcode), ringEntry.size, hit, std::nullopt, g_rings.getSamplingRate());
- }
}
}
}
}
}
+/* check that even if we group requestors to a /24, excluded IPs will not count toward
+ the group limit */
+BOOST_FIXTURE_TEST_CASE(test_DynBlockRulesGroup_QueryRate_ExcludedSubnets, TestFixture)
+{
+ dnsheader dnsHeader{};
+ memset(&dnsHeader, 0, sizeof(dnsHeader));
+ 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;
+ dnsdist::Protocol protocol = dnsdist::Protocol::DoUDP;
+ dnsdist::Protocol outgoingProtocol = dnsdist::Protocol::DoUDP;
+ unsigned int responseTime = 0;
+ struct timespec now;
+ gettime(&now);
+ NetmaskTree<DynBlock, AddressAndPortRange> 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);
+ dbrg.setMasks(24, 128, 0);
+ dbrg.excludeRange(Netmask(requestor2, 32));
+
+ {
+ /* block above 50 qps for numberOfSeconds seconds, no warning */
+ DynBlockRulesGroup::DynBlockRule rule(reason, blockDuration, 50, 0, numberOfSeconds, action);
+ dbrg.setQueryRate(std::move(rule));
+ }
+
+ {
+ /* 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(), 0U);
+ dnsdist::DynamicBlocks::clearClientAddressDynamicRules();
+
+ for (size_t idx = 0; idx < numberOfQueries; idx++) {
+ g_rings.insertQuery(now, requestor1, qname, qtype, size, dnsHeader, protocol);
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
+
+ dbrg.apply(now);
+ BOOST_CHECK_EQUAL(dnsdist::DynamicBlocks::getClientAddressDynamicRules().size(), 1U);
+ BOOST_CHECK(dnsdist::DynamicBlocks::getClientAddressDynamicRules().lookup(requestor1) != nullptr);
+ BOOST_CHECK(dnsdist::DynamicBlocks::getClientAddressDynamicRules().lookup(requestor2) != nullptr);
+ const auto& block = dnsdist::DynamicBlocks::getClientAddressDynamicRules().lookup(requestor1)->second;
+ BOOST_CHECK_EQUAL(block.reason, reason);
+ BOOST_CHECK_EQUAL(static_cast<size_t>(block.until.tv_sec), now.tv_sec + blockDuration);
+ BOOST_CHECK(block.domain.empty());
+ BOOST_CHECK(block.action == action);
+ BOOST_CHECK_EQUAL(block.blocks, 0U);
+ BOOST_CHECK_EQUAL(block.warning, false);
+ }
+
+ {
+ /* now do the same but from an excluded requestor */
+ size_t numberOfQueries = (50 * numberOfSeconds) + 1;
+ g_rings.clear();
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), 0U);
+ dnsdist::DynamicBlocks::clearClientAddressDynamicRules();
+
+ for (size_t idx = 0; idx < numberOfQueries; idx++) {
+ g_rings.insertQuery(now, requestor2, qname, qtype, size, dnsHeader, protocol);
+ g_rings.insertResponse(now, requestor2, qname, qtype, responseTime, size, dnsHeader, backend, outgoingProtocol);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
+
+ dbrg.apply(now);
+ BOOST_CHECK_EQUAL(dnsdist::DynamicBlocks::getClientAddressDynamicRules().size(), 0U);
+ }
+}
+
BOOST_FIXTURE_TEST_CASE(test_DynBlockRulesGroup_QueryRate_RangeV6, TestFixture)
{
/* Check that we correctly group IPv6 addresses from the same /64 subnet into the same
self.assertEqual(query, receivedQuery)
self.assertEqual(receivedResponse, receivedResponse)
+class TestDynBlockGroupExcludedRange(DynBlocksTest):
+
+ _config_template = """
+ local dbr = dynBlockRulesGroup()
+ dbr:setQueryRate(%d, %d, "Exceeded query rate", %d)
+ dbr:setMasks(8, 128, 0)
+ dbr:excludeRange("127.0.0.1/32")
+
+ function maintenance()
+ dbr:apply()
+ end
+
+ newServer{address="127.0.0.1:%d"}
+ """
+
+ def testExcluded(self):
+ """
+ Dyn Blocks (group) : Excluded from the dynamic block rules (range)
+ """
+ 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.assertEqual(query, receivedQuery)
+ self.assertEqual(response, receivedResponse)
+ allowed = allowed + 1
+ else:
+ # the query has not reached the responder,
+ # let's clear the response queue
+ self.clearToResponderQueue()
+
+ # we should not have been blocked
+ self.assertEqual(allowed, sent)
+
+ waitForMaintenanceToRun()
+
+ # we should still not be blocked
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(receivedResponse, receivedResponse)
+
class TestDynBlockGroupNoOp(DynBlocksTest):
_config_template = """