summaryrefslogtreecommitdiffstats
path: root/test-dnsdistdynblocks_hh.cc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:34:30 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:34:30 +0000
commit4fc2f55f761d71aae1f145d5aa94ba929cc39676 (patch)
tree5c1e1db3b46dd4edbe11f612d93cb94b96891ce3 /test-dnsdistdynblocks_hh.cc
parentInitial commit. (diff)
downloaddnsdist-4fc2f55f761d71aae1f145d5aa94ba929cc39676.tar.xz
dnsdist-4fc2f55f761d71aae1f145d5aa94ba929cc39676.zip
Adding upstream version 1.7.3.upstream/1.7.3upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--test-dnsdistdynblocks_hh.cc1482
1 files changed, 1482 insertions, 0 deletions
diff --git a/test-dnsdistdynblocks_hh.cc b/test-dnsdistdynblocks_hh.cc
new file mode 100644
index 0000000..2e6a34e
--- /dev/null
+++ b/test-dnsdistdynblocks_hh.cc
@@ -0,0 +1,1482 @@
+
+#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;
+shared_ptr<BPFFilter> g_defaultBPFFilter{nullptr};
+
+BOOST_AUTO_TEST_SUITE(dnsdistdynblocks_hh)
+
+BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_QueryRate) {
+ dnsheader dh;
+ memset(&dh, 0, sizeof(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;
+ 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);
+
+ /* 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(), 0U);
+ g_dynblockNMG.setState(emptyNMG);
+
+ for (size_t idx = 0; idx < numberOfQueries; idx++) {
+ g_rings.insertQuery(now, requestor1, qname, qtype, size, dh, protocol);
+ /* we do not care about the response during that test, but we want to make sure
+ these do not interfere with the computation */
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), numberOfQueries);
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
+
+ dbrg.apply(now);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 0U);
+ 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(), 0U);
+ g_dynblockNMG.setState(emptyNMG);
+
+ for (size_t idx = 0; idx < numberOfQueries; idx++) {
+ g_rings.insertQuery(now, requestor1, qname, qtype, size, dh, protocol);
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
+
+ dbrg.apply(now);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 1U);
+ 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(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);
+ }
+
+ {
+ /* clear the rings and dynamic blocks */
+ g_rings.clear();
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), 0U);
+ g_dynblockNMG.setState(emptyNMG);
+
+ /* Insert 100 qps from a given client in the last 10s
+ this should trigger the rule */
+ size_t numberOfQueries = 100;
+
+ for (size_t timeIdx = 0; timeIdx < numberOfSeconds; timeIdx++) {
+ for (size_t idx = 0; idx < numberOfQueries; idx++) {
+ struct timespec when = now;
+ when.tv_sec -= (9 - timeIdx);
+ g_rings.insertQuery(when, requestor1, qname, qtype, size, dh, protocol);
+ g_rings.insertResponse(when, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+ }
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries * numberOfSeconds);
+
+ dbrg.apply(now);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 1U);
+
+ /* now we clean up the dynamic blocks, simulating an admin removing the block */
+ g_dynblockNMG.setState(emptyNMG);
+ /* we apply the rules again, but as if we were 20s in the future.
+ Since we have a time windows of 10s nothing should be added,
+ regardless of the number of queries
+ */
+ struct timespec later = now;
+ later.tv_sec += 20;
+ dbrg.apply(later);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 0U);
+
+ /* just in case */
+ g_dynblockNMG.setState(emptyNMG);
+
+ /* we apply the rules again, this tile as if we were 5s in the future.
+ Since we have a time windows of 10s, and 100 qps over 5s then 0 qps over 5s
+ is more than 50qps over 10s, the block should be added
+ */
+ later = now;
+ later.tv_sec += 5;
+ dbrg.apply(later);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 1U);
+
+ /* clean up */
+ g_dynblockNMG.setState(emptyNMG);
+
+ /* we apply the rules again, this tile as if we were 6s in the future.
+ Since we have a time windows of 10s, and 100 qps over 4s then 0 qps over 6s
+ is LESS than 50qps over 10s, the block should NOT be added
+ */
+ later = now;
+ later.tv_sec += 6;
+ dbrg.apply(later);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 0U);
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_QueryRate_RangeV6) {
+ /* Check that we correctly group IPv6 addresses from the same /64 subnet into the same
+ dynamic block entry, if instructed to do so */
+ dnsheader dh;
+ memset(&dh, 0, sizeof(dh));
+ DNSName qname("rings.powerdns.com.");
+ ComboAddress requestor1("2001:db8::1");
+ ComboAddress backend("2001:0db8:ffff:ffff:ffff:ffff:ffff:ffff");
+ 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(32, 64, 0);
+
+ /* 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(), 0U);
+ g_dynblockNMG.setState(emptyNMG);
+
+ for (size_t idx = 0; idx < numberOfQueries; idx++) {
+ g_rings.insertQuery(now, requestor1, qname, qtype, size, dh, protocol);
+ /* we do not care about the response during that test, but we want to make sure
+ these do not interfere with the computation */
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), numberOfQueries);
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
+
+ dbrg.apply(now);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 0U);
+ BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(AddressAndPortRange(requestor1, 128, 16)) == nullptr);
+ }
+
+ {
+ /* insert just above 50 qps from several clients in the same /64 IPv6 range 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);
+ g_dynblockNMG.setState(emptyNMG);
+
+ for (size_t idx = 0; idx < numberOfQueries; idx++) {
+ ComboAddress requestor("2001:db8::" + std::to_string(idx));
+ g_rings.insertQuery(now, requestor, qname, qtype, size, dh, protocol);
+ g_rings.insertResponse(now, requestor, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
+
+ dbrg.apply(now);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 1U);
+
+ {
+ /* beginning of the range should be blocked */
+ const auto& block = g_dynblockNMG.getLocal()->lookup(AddressAndPortRange(requestor1, 128, 16))->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);
+ }
+
+ {
+ /* end of the range should be blocked as well */
+ ComboAddress end("2001:0db8:0000:0000:ffff:ffff:ffff:ffff");
+ const auto& block = g_dynblockNMG.getLocal()->lookup(AddressAndPortRange(end, 128, 16))->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);
+ }
+
+ {
+ /* outside of the range should NOT */
+ ComboAddress out("2001:0db8:0000:0001::0");
+ BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(AddressAndPortRange(out, 128, 16)) == nullptr);
+ }
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_QueryRate_V4Ports) {
+ /* Check that we correctly split IPv4 addresses based on port ranges, when instructed to do so */
+ dnsheader dh;
+ memset(&dh, 0, sizeof(dh));
+ DNSName qname("rings.powerdns.com.");
+ ComboAddress requestor1("192.0.2.1:42");
+ ComboAddress backend("192.0.2.254");
+ uint16_t qtype = QType::AAAA;
+ uint16_t size = 42;
+ unsigned int responseTime = 0;
+ dnsdist::Protocol protocol = dnsdist::Protocol::DoUDP;
+ dnsdist::Protocol outgoingProtocol = dnsdist::Protocol::DoUDP;
+ 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);
+ /* split v4 by ports using a /2 (0 - 16383, 16384 - 32767, 32768 - 49151, 49152 - 65535) */
+ dbrg.setMasks(32, 128, 2);
+
+ /* 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(), 0U);
+ g_dynblockNMG.setState(emptyNMG);
+
+ for (size_t idx = 0; idx < numberOfQueries; idx++) {
+ g_rings.insertQuery(now, requestor1, qname, qtype, size, dh, protocol);
+ /* we do not care about the response during that test, but we want to make sure
+ these do not interfere with the computation */
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), numberOfQueries);
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
+
+ dbrg.apply(now);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 0U);
+ BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(AddressAndPortRange(requestor1, 128, 16)) == nullptr);
+ }
+
+ {
+ /* insert just above 50 qps from several clients in the same IPv4 port range 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);
+ g_dynblockNMG.setState(emptyNMG);
+
+ for (size_t idx = 0; idx < numberOfQueries; idx++) {
+ ComboAddress requestor("192.0.2.1:" + std::to_string(idx));
+ g_rings.insertQuery(now, requestor, qname, qtype, size, dh, protocol);
+ g_rings.insertResponse(now, requestor, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
+
+ dbrg.apply(now);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 1U);
+
+ {
+ /* beginning of the port range should be blocked */
+ const auto& block = g_dynblockNMG.getLocal()->lookup(AddressAndPortRange(ComboAddress("192.0.2.1:0"), 32, 16))->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);
+ }
+
+ {
+ /* end of the range should be blocked as well */
+ const auto& block = g_dynblockNMG.getLocal()->lookup(AddressAndPortRange(ComboAddress("192.0.2.1:16383"), 32, 16))->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);
+ }
+
+ {
+ /* outside of the range should not */
+ BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(AddressAndPortRange(ComboAddress("192.0.2.1:16384"), 32, 16)) == nullptr);
+ }
+
+ /* we (again) insert just above 50 qps from several clients the same IPv4 port range, this should update the block which will
+ check by looking at the blocked counter */
+ {
+ auto block = g_dynblockNMG.getLocal()->lookup(AddressAndPortRange(ComboAddress("192.0.2.1:0"), 32, 16));
+ BOOST_REQUIRE(block != nullptr);
+ BOOST_CHECK_EQUAL(block->second.blocks, 0U);
+ block->second.blocks = 42U;
+ }
+
+ g_rings.clear();
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), 0U);
+
+ for (size_t idx = 0; idx < numberOfQueries; idx++) {
+ ComboAddress requestor("192.0.2.1:" + std::to_string(idx));
+ g_rings.insertQuery(now, requestor, qname, qtype, size, dh, protocol);
+ g_rings.insertResponse(now, requestor, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
+
+ dbrg.apply(now);
+
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 1U);
+ {
+ /* previous address/port should still be blocked */
+ auto block = g_dynblockNMG.getLocal()->lookup(AddressAndPortRange(ComboAddress("192.0.2.1:0"), 32, 16));
+ BOOST_REQUIRE(block != nullptr);
+ BOOST_CHECK_EQUAL(block->second.blocks, 42U);
+ }
+
+ /* but not a different one */
+ BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(AddressAndPortRange(ComboAddress("192.0.2.1:16384"), 32, 16)) == nullptr);
+
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_QueryRate_responses) {
+ /* check that the responses are not accounted as queries when a
+ rcode rate rule is defined (sounds very specific but actually happened) */
+ dnsheader dh;
+ memset(&dh, 0, sizeof(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;
+ 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;
+
+ /* 100k entries, one shard */
+ g_rings.setCapacity(1000000, 1);
+
+ size_t numberOfSeconds = 10;
+ size_t blockDuration = 60;
+ const auto action = DNSAction::Action::Drop;
+ const std::string reason = "Exceeded query rate";
+
+ /* 100k entries, one shard */
+ g_rings.setCapacity(1000000, 1);
+
+ DynBlockRulesGroup dbrg;
+ dbrg.setQuiet(true);
+
+ /* block above 50 qps for numberOfSeconds seconds, no warning */
+ dbrg.setQueryRate(50, 0, numberOfSeconds, reason, blockDuration, action);
+ dbrg.setRCodeRate(RCode::ServFail, 50, 40, 5, "Exceeded ServFail rate", 60, DNSAction::Action::Drop);
+
+ {
+ /* insert 45 qps (including responses) from a given client for the last 100s
+ this should not trigger the rule */
+ size_t numberOfQueries = 45;
+ g_rings.clear();
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), 0U);
+ g_dynblockNMG.setState(emptyNMG);
+
+ for (size_t timeIdx = 0; timeIdx < 100; timeIdx++) {
+ struct timespec when = now;
+ when.tv_sec -= (99 - timeIdx);
+ for (size_t idx = 0; idx < numberOfQueries; idx++) {
+ g_rings.insertQuery(when, requestor1, qname, qtype, size, dh, protocol);
+ /* we do not care about the response during that test, but we want to make sure
+ these do not interfere with the computation */
+ g_rings.insertResponse(when, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+ }
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), numberOfQueries * 100);
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries * 100);
+
+ dbrg.apply(now);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 0U);
+ BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor1) == nullptr);
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_QTypeRate) {
+ dnsheader dh;
+ memset(&dh, 0, sizeof(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;
+ dnsdist::Protocol protocol = dnsdist::Protocol::DoUDP;
+ 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);
+
+ /* 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(), 0U);
+ g_dynblockNMG.setState(emptyNMG);
+
+ for (size_t idx = 0; idx < numberOfQueries; idx++) {
+ g_rings.insertQuery(now, requestor1, qname, qtype, size, dh, protocol);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
+
+ dbrg.apply(now);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 0U);
+ 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(), 0U);
+ g_dynblockNMG.setState(emptyNMG);
+
+ for (size_t idx = 0; idx < numberOfQueries; idx++) {
+ g_rings.insertQuery(now, requestor1, qname, QType::A, size, dh, protocol);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
+
+ dbrg.apply(now);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 0U);
+ 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(), 0U);
+ g_dynblockNMG.setState(emptyNMG);
+
+ for (size_t idx = 0; idx < numberOfQueries; idx++) {
+ g_rings.insertQuery(now, requestor1, qname, qtype, size, dh, protocol);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
+
+ dbrg.apply(now);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 1U);
+ 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(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);
+ }
+
+}
+
+BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_RCodeRate) {
+ dnsheader dh;
+ memset(&dh, 0, sizeof(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;
+ dnsdist::Protocol outgoingProtocol = dnsdist::Protocol::DoUDP;
+ unsigned int responseTime = 100 * 1000; /* 100ms */
+ 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";
+ 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(), 0U);
+ 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, outgoingProtocol);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), numberOfResponses);
+
+ dbrg.apply(now);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 0U);
+ 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(), 0U);
+ 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, outgoingProtocol);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), numberOfResponses);
+
+ dbrg.apply(now);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 0U);
+ 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(), 0U);
+ 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, outgoingProtocol);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), numberOfResponses);
+
+ dbrg.apply(now);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 1U);
+ 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(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);
+ }
+
+}
+
+BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_RCodeRatio) {
+ dnsheader dh;
+ memset(&dh, 0, sizeof(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;
+ dnsdist::Protocol outgoingProtocol = dnsdist::Protocol::DoUDP;
+ unsigned int responseTime = 100 * 1000; /* 100ms */
+ struct timespec now;
+ gettime(&now);
+ NetmaskTree<DynBlock, AddressAndPortRange> emptyNMG;
+
+ time_t numberOfSeconds = 10;
+ unsigned int blockDuration = 60;
+ const auto action = DNSAction::Action::Drop;
+ const std::string reason = "Exceeded query ratio";
+ const uint16_t rcode = RCode::ServFail;
+
+ DynBlockRulesGroup dbrg;
+ dbrg.setQuiet(true);
+
+ /* block above 0.2 ServFail/Total ratio over numberOfSeconds seconds, no warning, minimum number of queries should be at least 51 */
+ dbrg.setRCodeRatio(rcode, 0.2, 0, numberOfSeconds, reason, blockDuration, action, 51);
+
+ {
+ /* insert 20 ServFail and 80 NoErrors from a given client in the last 10s
+ this should not trigger the rule */
+ g_rings.clear();
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 0U);
+ g_dynblockNMG.setState(emptyNMG);
+
+ dh.rcode = rcode;
+ for (size_t idx = 0; idx < 20; idx++) {
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+ }
+ dh.rcode = RCode::NoError;
+ for (size_t idx = 0; idx < 80; idx++) {
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 100U);
+
+ dbrg.apply(now);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 0U);
+ BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor1) == nullptr);
+ }
+
+ {
+ /* insert just 50 FormErrs and nothing else, from a given client in the last 10s */
+ g_rings.clear();
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 0U);
+ g_dynblockNMG.setState(emptyNMG);
+
+ dh.rcode = RCode::FormErr;
+ for (size_t idx = 0; idx < 50; idx++) {
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 50U);
+
+ dbrg.apply(now);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 0U);
+ BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor1) == nullptr);
+ }
+
+ {
+ /* insert 21 ServFails and 79 NoErrors from a given client in the last 10s
+ this should trigger the rule this time */
+ g_rings.clear();
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 0U);
+ g_dynblockNMG.setState(emptyNMG);
+
+ dh.rcode = rcode;
+ for (size_t idx = 0; idx < 21; idx++) {
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+ }
+ dh.rcode = RCode::NoError;
+ for (size_t idx = 0; idx < 79; idx++) {
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 100U);
+
+ dbrg.apply(now);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 1U);
+ BOOST_REQUIRE(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, 0U);
+ BOOST_CHECK_EQUAL(block.warning, false);
+ }
+
+ {
+ /* insert 11 ServFails and 39 NoErrors from a given client in the last 10s
+ this should NOT trigger the rule since we don't have more than 50 queries */
+ g_rings.clear();
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 0U);
+ g_dynblockNMG.setState(emptyNMG);
+
+ dh.rcode = rcode;
+ for (size_t idx = 0; idx < 11; idx++) {
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+ }
+ dh.rcode = RCode::NoError;
+ for (size_t idx = 0; idx < 39; idx++) {
+ g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend, outgoingProtocol);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 50U);
+
+ dbrg.apply(now);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 0U);
+ BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor1) == nullptr);
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_ResponseByteRate) {
+ dnsheader dh;
+ memset(&dh, 0, sizeof(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;
+ dnsdist::Protocol outgoingProtocol = dnsdist::Protocol::DoUDP;
+ unsigned int responseTime = 100 * 1000; /* 100ms */
+ 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";
+ 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(), 0U);
+ 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, outgoingProtocol);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), numberOfResponses);
+
+ dbrg.apply(now);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 0U);
+ 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(), 0U);
+ 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, outgoingProtocol);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), numberOfResponses);
+
+ dbrg.apply(now);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 1U);
+ 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(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);
+ }
+
+}
+
+BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_Warning) {
+ dnsheader dh;
+ memset(&dh, 0, sizeof(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;
+ dnsdist::Protocol protocol = dnsdist::Protocol::DoUDP;
+ 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);
+
+ /* 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(), 0U);
+ g_dynblockNMG.setState(emptyNMG);
+
+ for (size_t idx = 0; idx < numberOfQueries; idx++) {
+ g_rings.insertQuery(now, requestor1, qname, qtype, size, dh, protocol);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
+
+ dbrg.apply(now);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 0U);
+ 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(), 0U);
+ g_dynblockNMG.setState(emptyNMG);
+
+ for (size_t idx = 0; idx < numberOfQueries; idx++) {
+ g_rings.insertQuery(now, requestor1, qname, qtype, size, dh, protocol);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
+
+ dbrg.apply(now);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 1U);
+ 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(static_cast<size_t>(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, 0U);
+ 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(), 0U);
+
+ for (size_t idx = 0; idx < numberOfQueries; idx++) {
+ g_rings.insertQuery(now, requestor1, qname, qtype, size, dh, protocol);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
+
+ dbrg.apply(now);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 1U);
+ 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(static_cast<size_t>(block.until.tv_sec), now.tv_sec + blockDuration);
+ BOOST_CHECK(block.domain.empty());
+ BOOST_CHECK(block.action == action);
+ /* this should have been preserved */
+ BOOST_CHECK_EQUAL(block.blocks, 1U);
+ 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(), 0U);
+
+ for (size_t idx = 0; idx < numberOfQueries; idx++) {
+ g_rings.insertQuery(now, requestor1, qname, qtype, size, dh, protocol);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
+
+ dbrg.apply(now);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 1U);
+ 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(static_cast<size_t>(block.until.tv_sec), now.tv_sec + blockDuration);
+ BOOST_CHECK(block.domain.empty());
+ BOOST_CHECK(block.action == action);
+ /* this should have been preserved */
+ BOOST_CHECK_EQUAL(block.blocks, 2U);
+ 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(), 0U);
+ g_dynblockNMG.setState(emptyNMG);
+
+ for (size_t idx = 0; idx < numberOfQueries; idx++) {
+ g_rings.insertQuery(now, requestor1, qname, qtype, size, dh, protocol);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries);
+
+ dbrg.apply(now);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 1U);
+ 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(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);
+ }
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_Ranges) {
+ dnsheader dh;
+ memset(&dh, 0, sizeof(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;
+ dnsdist::Protocol protocol = dnsdist::Protocol::DoUDP;
+ 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);
+ /* 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(), 0U);
+ g_dynblockNMG.setState(emptyNMG);
+
+ for (size_t idx = 0; idx < numberOfQueries; idx++) {
+ g_rings.insertQuery(now, requestor1, qname, qtype, size, dh, protocol);
+ g_rings.insertQuery(now, requestor2, qname, qtype, size, dh, protocol);
+ }
+ BOOST_CHECK_EQUAL(g_rings.getNumberOfQueryEntries(), numberOfQueries * 2);
+
+ dbrg.apply(now);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 1U);
+ 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(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);
+ }
+
+}
+
+BOOST_AUTO_TEST_CASE(test_DynBlockRulesMetricsCache_GetTopN) {
+ dnsheader dh;
+ memset(&dh, 0, sizeof(dh));
+ DNSName qname("rings.powerdns.com.");
+ uint16_t qtype = QType::AAAA;
+ uint16_t size = 42;
+ dnsdist::Protocol protocol = dnsdist::Protocol::DoUDP;
+ dnsdist::Protocol outgoingProtocol = dnsdist::Protocol::DoUDP;
+ struct timespec now;
+ gettime(&now);
+ NetmaskTree<DynBlock, AddressAndPortRange> emptyNMG;
+ SuffixMatchTree<DynBlock> emptySMT;
+
+ size_t numberOfSeconds = 10;
+ size_t blockDuration = 60;
+ const auto action = DNSAction::Action::Drop;
+ const std::string reason = "Exceeded query rate";
+
+ /* 10M entries, only one shard */
+ g_rings.setCapacity(10000000, 1);
+
+ {
+ DynBlockRulesGroup dbrg;
+ dbrg.setQuiet(true);
+ g_rings.clear();
+ g_dynblockNMG.setState(emptyNMG);
+
+ /* block above 0 qps for numberOfSeconds seconds, no warning */
+ dbrg.setQueryRate(0, 0, numberOfSeconds, reason, blockDuration, action);
+
+ /* insert one fake query from 255 clients:
+ */
+ for (size_t idx = 0; idx < 256; idx++) {
+ const ComboAddress requestor("192.0.2." + std::to_string(idx));
+ g_rings.insertQuery(now, requestor, qname, qtype, size, dh, protocol);
+ }
+
+ /* we apply the rules, all clients should be blocked */
+ dbrg.apply(now);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 256U);
+
+ for (size_t idx = 0; idx < 256; idx++) {
+ const ComboAddress requestor("192.0.2." + std::to_string(idx));
+ const auto& block = g_dynblockNMG.getLocal()->lookup(requestor)->second;
+ /* simulate that:
+ - .1 does 1 query
+ ...
+ - .255 does 255 queries
+ */
+ block.blocks = idx;
+ }
+
+ /* now we ask for the top 20 offenders for each reason */
+ StopWatch sw;
+ sw.start();
+ auto top = DynBlockMaintenance::getTopNetmasks(20);
+ BOOST_REQUIRE_EQUAL(top.size(), 1U);
+ auto offenders = top.at(reason);
+ BOOST_REQUIRE_EQUAL(offenders.size(), 20U);
+ auto it = offenders.begin();
+ for (size_t idx = 236; idx < 256; idx++) {
+ BOOST_CHECK_EQUAL(it->first.toString(), Netmask(ComboAddress("192.0.2." + std::to_string(idx))).toString());
+ BOOST_CHECK_EQUAL(it->second, idx);
+ ++it;
+ }
+
+ struct timespec expired = now;
+ expired.tv_sec += blockDuration + 1;
+ DynBlockMaintenance::purgeExpired(expired);
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 0U);
+ }
+
+ {
+ /* === reset everything for SMT === */
+ DynBlockRulesGroup dbrg;
+ dbrg.setQuiet(true);
+ g_rings.clear();
+ g_dynblockNMG.setState(emptyNMG);
+ g_dynblockSMT.setState(emptySMT);
+
+ dbrg.setSuffixMatchRule(numberOfSeconds, reason, blockDuration, action, [](const StatNode& node, const StatNode::Stat& self, const StatNode::Stat& children) {
+ if (self.queries > 0) {
+ return std::tuple<bool, boost::optional<std::string>>(true, boost::none);
+ }
+ return std::tuple<bool, boost::optional<std::string>>(false, boost::none);
+ });
+
+ /* insert one fake response for 255 DNS names */
+ const ComboAddress requestor("192.0.2.1");
+ for (size_t idx = 0; idx < 256; idx++) {
+ g_rings.insertResponse(now, requestor, DNSName(std::to_string(idx)) + qname, qtype, 1000 /*usec*/, size, dh, requestor /* backend, technically, but we don't care */, outgoingProtocol);
+ }
+
+ /* we apply the rules, all suffixes should be blocked */
+ dbrg.apply(now);
+
+ for (size_t idx = 0; idx < 256; idx++) {
+ const DNSName name(DNSName(std::to_string(idx)) + qname);
+ const auto* block = g_dynblockSMT.getLocal()->lookup(name);
+ BOOST_REQUIRE(block != nullptr);
+ /* simulate that:
+ - 1.rings.powerdns.com. got 1 query
+ ...
+ - 255. does 255 queries
+ */
+ block->blocks = idx;
+ }
+
+ /* now we ask for the top 20 offenders for each reason */
+ StopWatch sw;
+ sw.start();
+ auto top = DynBlockMaintenance::getTopSuffixes(20);
+ BOOST_REQUIRE_EQUAL(top.size(), 1U);
+ auto suffixes = top.at(reason);
+ BOOST_REQUIRE_EQUAL(suffixes.size(), 20U);
+ auto it = suffixes.begin();
+ for (size_t idx = 236; idx < 256; idx++) {
+ BOOST_CHECK_EQUAL(it->first, (DNSName(std::to_string(idx)) + qname));
+ BOOST_CHECK_EQUAL(it->second, idx);
+ ++it;
+ }
+
+ struct timespec expired = now;
+ expired.tv_sec += blockDuration + 1;
+ DynBlockMaintenance::purgeExpired(expired);
+ BOOST_CHECK(g_dynblockSMT.getLocal()->getNodes().empty());
+ }
+
+ {
+ /* === reset everything for SMT, this time we will check that we can override the 'reason' via the visitor function === */
+ DynBlockRulesGroup dbrg;
+ dbrg.setQuiet(true);
+ g_rings.clear();
+ g_dynblockNMG.setState(emptyNMG);
+ g_dynblockSMT.setState(emptySMT);
+
+ dbrg.setSuffixMatchRule(numberOfSeconds, reason, blockDuration, action, [](const StatNode& node, const StatNode::Stat& self, const StatNode::Stat& children) {
+ if (self.queries > 0) {
+ return std::tuple<bool, boost::optional<std::string>>(true, "blocked for a different reason");
+ }
+ return std::tuple<bool, boost::optional<std::string>>(false, boost::none);
+ });
+
+ /* insert one fake response for 255 DNS names */
+ const ComboAddress requestor("192.0.2.1");
+ for (size_t idx = 0; idx < 256; idx++) {
+ g_rings.insertResponse(now, requestor, DNSName(std::to_string(idx)) + qname, qtype, 1000 /*usec*/, size, dh, requestor /* backend, technically, but we don't care */, dnsdist::Protocol::DoUDP);
+ }
+
+ /* we apply the rules, all suffixes should be blocked */
+ dbrg.apply(now);
+
+ for (size_t idx = 0; idx < 256; idx++) {
+ const DNSName name(DNSName(std::to_string(idx)) + qname);
+ const auto* block = g_dynblockSMT.getLocal()->lookup(name);
+ BOOST_REQUIRE(block != nullptr);
+ /* simulate that:
+ - 1.rings.powerdns.com. got 1 query
+ ...
+ - 255. does 255 queries
+ */
+ block->blocks = idx;
+ }
+
+ /* now we ask for the top 20 offenders for each reason */
+ StopWatch sw;
+ sw.start();
+ auto top = DynBlockMaintenance::getTopSuffixes(20);
+ BOOST_REQUIRE_EQUAL(top.size(), 1U);
+ auto suffixes = top.at("blocked for a different reason");
+ BOOST_REQUIRE_EQUAL(suffixes.size(), 20U);
+ auto it = suffixes.begin();
+ for (size_t idx = 236; idx < 256; idx++) {
+ BOOST_CHECK_EQUAL(it->first, (DNSName(std::to_string(idx)) + qname));
+ BOOST_CHECK_EQUAL(it->second, idx);
+ ++it;
+ }
+
+ struct timespec expired = now;
+ expired.tv_sec += blockDuration + 1;
+ DynBlockMaintenance::purgeExpired(expired);
+ BOOST_CHECK(g_dynblockSMT.getLocal()->getNodes().empty());
+ }
+
+#ifdef BENCH_DYNBLOCKS
+ {
+ /* now insert 1M names */
+ DynBlockRulesGroup dbrg;
+ dbrg.setQuiet(true);
+ g_rings.clear();
+ g_dynblockNMG.setState(emptyNMG);
+ g_dynblockSMT.setState(emptySMT);
+
+ dbrg.setSuffixMatchRule(numberOfSeconds, reason, blockDuration, action, [](const StatNode& node, const StatNode::Stat& self, const StatNode::Stat& children) {
+ if (self.queries > 0) {
+ return std::tuple<bool, boost::optional<std::string>>(true, boost::none);
+ }
+ return std::tuple<bool, boost::optional<std::string>>(false, boost::none);
+ });
+
+ bool done = false;
+ const ComboAddress requestor("192.0.2.1");
+ for (size_t idxB = 0; !done && idxB < 256; idxB++) {
+ for (size_t idxC = 0; !done && idxC < 256; idxC++) {
+ for (size_t idxD = 0; !done && idxD < 256; idxD++) {
+ const DNSName victim(std::to_string(idxB) + "." + std::to_string(idxC) + "." + std::to_string(idxD) + qname.toString());
+ g_rings.insertResponse(now, requestor, victim, qtype, 1000 /*usec*/, size, dh, requestor /* backend, technically, but we don't care */, outgoingProtocol);
+ if (g_rings.getNumberOfQueryEntries() == 1000000) {
+ done = true;
+ break;
+ }
+ }
+ }
+ }
+
+ /* we apply the rules, all suffixes should be blocked */
+ StopWatch sw;
+ sw.start();
+ dbrg.apply(now);
+ cerr<<"added 1000000 entries in "<<std::to_string(sw.udiff()/1024)<<"ms"<<endl;
+
+ sw.start();
+ auto top = DynBlockMaintenance::getTopSuffixes(20);
+ cerr<<"scanned 1000000 entries in "<<std::to_string(sw.udiff()/1024)<<"ms"<<endl;
+ BOOST_CHECK_EQUAL(top.at(reason).size(), 20U);
+ BOOST_CHECK_EQUAL(top.size(), 1U);
+
+ struct timespec expired = now;
+ expired.tv_sec += blockDuration + 1;
+ sw.start();
+ DynBlockMaintenance::purgeExpired(expired);
+ cerr<<"removed 1000000 entries in "<<std::to_string(sw.udiff()/1024)<<"ms"<<endl;
+ BOOST_CHECK_EQUAL(g_dynblockSMT.getLocal()->getNodes().size(), 0U);
+ }
+#endif
+
+#ifdef BENCH_DYNBLOCKS
+ {
+ /* now insert 1M clients */
+ DynBlockRulesGroup dbrg;
+ dbrg.setQuiet(true);
+ g_rings.clear();
+ g_dynblockNMG.setState(emptyNMG);
+ g_dynblockSMT.setState(emptySMT);
+ dbrg.setQueryRate(0, 0, numberOfSeconds, reason, blockDuration, action);
+
+ bool done = false;
+ for (size_t idxB = 0; !done && idxB < 256; idxB++) {
+ for (size_t idxC = 0; !done && idxC < 256; idxC++) {
+ for (size_t idxD = 0; !done && idxD < 256; idxD++) {
+ const ComboAddress requestor("192." + std::to_string(idxB) + "." + std::to_string(idxC) + "." + std::to_string(idxD));
+ g_rings.insertQuery(now, requestor, qname, qtype, size, dh, protocol);
+ if (g_rings.getNumberOfQueryEntries() == 1000000) {
+ done = true;
+ break;
+ }
+ }
+ }
+ }
+
+ /* we apply the rules, all clients should be blocked */
+ StopWatch sw;
+ sw.start();
+ dbrg.apply(now);
+ cerr<<"added "<<g_dynblockNMG.getLocal()->size()<<" entries in "<<std::to_string(sw.udiff()/1024)<<"ms"<<endl;
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 1000000U);
+
+ sw.start();
+ auto top = DynBlockMaintenance::getTopNetmasks(20);
+ cerr<<"scanned "<<g_dynblockNMG.getLocal()->size()<<" entries in "<<std::to_string(sw.udiff()/1024)<<"ms"<<endl;
+
+ struct timespec expired = now;
+ expired.tv_sec += blockDuration + 1;
+ sw.start();
+ DynBlockMaintenance::purgeExpired(expired);
+ cerr<<"removed 1000000 entries in "<<std::to_string(sw.udiff()/1024)<<"ms"<<endl;
+ BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 0U);
+ }
+#endif
+}
+
+BOOST_AUTO_TEST_CASE(test_NetmaskTree) {
+ NetmaskTree<int, AddressAndPortRange> nmt;
+ BOOST_CHECK_EQUAL(nmt.empty(), true);
+ BOOST_CHECK_EQUAL(nmt.size(), 0U);
+ nmt.insert(AddressAndPortRange(ComboAddress("130.161.252.0"), 24, 0)).second = 0;
+ BOOST_CHECK_EQUAL(nmt.empty(), false);
+ BOOST_CHECK_EQUAL(nmt.size(), 1U);
+ nmt.insert(AddressAndPortRange(ComboAddress("130.161.0.0"), 16, 0)).second = 1;
+ BOOST_CHECK_EQUAL(nmt.size(), 2U);
+ nmt.insert(AddressAndPortRange(ComboAddress("130.0.0.0"), 8, 0)).second = 2;
+ BOOST_CHECK_EQUAL(nmt.size(), 3U);
+
+ BOOST_CHECK(nmt.lookup(ComboAddress("213.244.168.210")) == nullptr);
+ auto found = nmt.lookup(ComboAddress("130.161.252.29"));
+ BOOST_REQUIRE(found);
+ BOOST_CHECK_EQUAL(found->second, 0);
+ found = nmt.lookup(ComboAddress("130.161.180.1"));
+ BOOST_CHECK(found);
+ BOOST_CHECK_EQUAL(found->second, 1);
+
+ BOOST_CHECK_EQUAL(nmt.lookup(ComboAddress("130.255.255.255"))->second, 2);
+ BOOST_CHECK_EQUAL(nmt.lookup(ComboAddress("130.161.252.255"))->second, 0);
+ BOOST_CHECK_EQUAL(nmt.lookup(ComboAddress("130.161.253.255"))->second, 1);
+ BOOST_CHECK_EQUAL(nmt.lookup(AddressAndPortRange(ComboAddress("130.255.255.255"), 32, 16))->second, 2);
+ BOOST_CHECK_EQUAL(nmt.lookup(AddressAndPortRange(ComboAddress("130.161.252.255"), 32, 16))->second, 0);
+ BOOST_CHECK_EQUAL(nmt.lookup(AddressAndPortRange(ComboAddress("130.161.253.255"), 32, 16))->second, 1);
+
+ found = nmt.lookup(ComboAddress("130.145.180.1"));
+ BOOST_CHECK(found);
+ BOOST_CHECK_EQUAL(found->second, 2);
+
+ nmt.insert(AddressAndPortRange(ComboAddress("0.0.0.0"), 0, 0)).second = 3;
+ BOOST_CHECK_EQUAL(nmt.size(), 4U);
+ nmt.insert(AddressAndPortRange(ComboAddress("0.0.0.0"), 7, 0)).second = 4;
+ BOOST_CHECK_EQUAL(nmt.size(), 5U);
+ nmt.insert(AddressAndPortRange(ComboAddress("0.0.0.0"), 15, 0)).second = 5;
+ BOOST_CHECK_EQUAL(nmt.size(), 6U);
+ BOOST_CHECK_EQUAL(nmt.lookup(AddressAndPortRange(ComboAddress("0.0.0.0"), 0, 0))->second, 3);
+ BOOST_CHECK_EQUAL(nmt.lookup(AddressAndPortRange(ComboAddress("0.0.0.0"), 7, 0))->second, 4);
+ BOOST_CHECK_EQUAL(nmt.lookup(AddressAndPortRange(ComboAddress("0.0.0.0"), 15, 0))->second, 5);
+ BOOST_CHECK_EQUAL(nmt.lookup(AddressAndPortRange(ComboAddress("0.0.0.0"), 32, 0))->second, 5);
+
+ nmt.clear();
+ BOOST_CHECK_EQUAL(nmt.empty(), true);
+ BOOST_CHECK_EQUAL(nmt.size(), 0U);
+ BOOST_CHECK(!nmt.lookup(ComboAddress("130.161.180.1")));
+
+ nmt.insert(AddressAndPortRange(ComboAddress("::1"), 128, 0)).second = 1;
+ BOOST_CHECK_EQUAL(nmt.empty(), false);
+ BOOST_CHECK_EQUAL(nmt.size(), 1U);
+ nmt.insert(AddressAndPortRange(ComboAddress("::"), 0, 0)).second = 0;
+ BOOST_CHECK_EQUAL(nmt.size(), 2U);
+ nmt.insert(AddressAndPortRange(ComboAddress("fe80::"), 16, 0)).second = 2;
+ BOOST_CHECK_EQUAL(nmt.size(), 3U);
+ BOOST_CHECK(nmt.lookup(ComboAddress("130.161.253.255")) == nullptr);
+ BOOST_CHECK_EQUAL(nmt.lookup(ComboAddress("::2"))->second, 0);
+ BOOST_CHECK_EQUAL(nmt.lookup(ComboAddress("::ffff"))->second, 0);
+ BOOST_CHECK_EQUAL(nmt.lookup(ComboAddress("::1"))->second, 1);
+ BOOST_CHECK_EQUAL(nmt.lookup(ComboAddress("fe80::1"))->second, 2);
+}
+
+BOOST_AUTO_TEST_CASE(test_NetmaskTreePort) {
+ {
+ /* exact port matching */
+ NetmaskTree<int, AddressAndPortRange> nmt;
+ BOOST_CHECK_EQUAL(nmt.empty(), true);
+ BOOST_CHECK_EQUAL(nmt.size(), 0U);
+ nmt.insert(AddressAndPortRange(ComboAddress("130.161.252.42:65534"), 32, 16)).second = 0;
+ BOOST_CHECK_EQUAL(nmt.empty(), false);
+ BOOST_CHECK_EQUAL(nmt.size(), 1U);
+
+ BOOST_CHECK(nmt.lookup(AddressAndPortRange(ComboAddress("213.244.168.210"), 32, 16)) == nullptr);
+
+ auto found = nmt.lookup(AddressAndPortRange(ComboAddress("130.161.252.42:65534"), 32, 16));
+ BOOST_CHECK(found != nullptr);
+ BOOST_CHECK(nmt.lookup(AddressAndPortRange(ComboAddress("130.161.252.42:65533"), 32, 16)) == nullptr);
+ BOOST_CHECK(nmt.lookup(AddressAndPortRange(ComboAddress("130.161.252.42:65535"), 32, 16)) == nullptr);
+ }
+
+ {
+ /* /15 port matching */
+ NetmaskTree<int, AddressAndPortRange> nmt;
+ BOOST_CHECK_EQUAL(nmt.empty(), true);
+ BOOST_CHECK_EQUAL(nmt.size(), 0U);
+ nmt.insert(AddressAndPortRange(ComboAddress("130.161.252.42:0"), 32, 15)).second = 0;
+ BOOST_CHECK_EQUAL(nmt.empty(), false);
+ BOOST_CHECK_EQUAL(nmt.size(), 1U);
+
+ BOOST_CHECK(nmt.lookup(AddressAndPortRange(ComboAddress("213.244.168.210"), 32, 16)) == nullptr);
+
+ auto found = nmt.lookup(AddressAndPortRange(ComboAddress("130.161.252.42:0"), 32, 16));
+ BOOST_CHECK(found != nullptr);
+
+ found = nmt.lookup(AddressAndPortRange(ComboAddress("130.161.252.42:1"), 32, 16));
+ BOOST_CHECK(found != nullptr);
+
+ /* everything else should be a miss */
+ for (size_t idx = 2; idx <= 65535; idx++) {
+ BOOST_CHECK(nmt.lookup(AddressAndPortRange(ComboAddress("130.161.252.42:" + std::to_string(idx)), 32, 16)) == nullptr);
+ }
+
+ nmt.clear();
+ BOOST_CHECK_EQUAL(nmt.empty(), true);
+ BOOST_CHECK_EQUAL(nmt.size(), 0U);
+ nmt.insert(AddressAndPortRange(ComboAddress("130.161.252.42:65535"), 32, 15)).second = 0;
+ BOOST_CHECK_EQUAL(nmt.empty(), false);
+ BOOST_CHECK_EQUAL(nmt.size(), 1U);
+
+ BOOST_CHECK(nmt.lookup(AddressAndPortRange(ComboAddress("213.244.168.210"), 32, 16)) == nullptr);
+
+ /* everything else should be a miss */
+ for (size_t idx = 0; idx <= 65533; idx++) {
+ BOOST_CHECK(nmt.lookup(AddressAndPortRange(ComboAddress("130.161.252.42:" + std::to_string(idx)), 32, 16)) == nullptr);
+ }
+ found = nmt.lookup(AddressAndPortRange(ComboAddress("130.161.252.42:65534"), 32, 16));
+ BOOST_CHECK(found != nullptr);
+ found = nmt.lookup(AddressAndPortRange(ComboAddress("130.161.252.42:65535"), 32, 16));
+ BOOST_CHECK(found != nullptr);
+ }
+
+ {
+ /* /1 port matching */
+ NetmaskTree<int, AddressAndPortRange> nmt;
+ BOOST_CHECK_EQUAL(nmt.empty(), true);
+ BOOST_CHECK_EQUAL(nmt.size(), 0U);
+ nmt.insert(AddressAndPortRange(ComboAddress("130.161.252.42:0"), 32, 1)).second = 0;
+ BOOST_CHECK_EQUAL(nmt.empty(), false);
+ BOOST_CHECK_EQUAL(nmt.size(), 1U);
+
+ BOOST_CHECK(nmt.lookup(AddressAndPortRange(ComboAddress("213.244.168.210"), 32, 16)) == nullptr);
+
+ for (size_t idx = 0; idx <= 32767; idx++) {
+ auto found = nmt.lookup(AddressAndPortRange(ComboAddress("130.161.252.42:" + std::to_string(idx)), 32, 16));
+ BOOST_CHECK(found != nullptr);
+ }
+
+ /* everything else should be a miss */
+ for (size_t idx = 32768; idx <= 65535; idx++) {
+ BOOST_CHECK(nmt.lookup(AddressAndPortRange(ComboAddress("130.161.252.42:" + std::to_string(idx)), 32, 16)) == nullptr);
+ }
+ }
+
+ {
+ /* Check that the port matching does not apply to IPv6, where it does not make sense */
+
+ /* /1 port matching */
+ NetmaskTree<int, AddressAndPortRange> nmt;
+ BOOST_CHECK_EQUAL(nmt.empty(), true);
+ BOOST_CHECK_EQUAL(nmt.size(), 0U);
+ nmt.insert(AddressAndPortRange(ComboAddress("[2001:db8::1]:0"), 128, 1)).second = 0;
+ BOOST_CHECK_EQUAL(nmt.empty(), false);
+ BOOST_CHECK_EQUAL(nmt.size(), 1U);
+
+ /* different IP, no match */
+ BOOST_CHECK(nmt.lookup(AddressAndPortRange(ComboAddress("[2001:db8::2]:0"), 128, 16)) == nullptr);
+
+ /* all ports should match */
+ for (size_t idx = 1; idx <= 65535; idx++) {
+ auto found = nmt.lookup(AddressAndPortRange(ComboAddress("[2001:db8::1]:" + std::to_string(idx)), 128, 16));
+ BOOST_CHECK(found != nullptr);
+ }
+ }
+}
+
+BOOST_AUTO_TEST_SUITE_END()