#ifndef BOOST_TEST_DYN_LINK #define BOOST_TEST_DYN_LINK #endif #define BOOST_TEST_NO_MAIN #include #include #include "dnsdist-rules.hh" void checkParameterBound(const std::string& parameter, uint64_t value, size_t max); void checkParameterBound(const std::string& parameter, uint64_t value, size_t max) { if (value > max) { throw std::runtime_error("The value passed to " + parameter + " is too large, the maximum is " + std::to_string(max)); } } static DNSQuestion getDQ(const DNSName* providedName = nullptr) { static const DNSName qname("powerdns.com."); static PacketBuffer packet(sizeof(dnsheader)); static InternalQueryState ids; ids.origDest = ComboAddress("127.0.0.1:53"); ids.origRemote = ComboAddress("192.0.2.1:42"); ids.qname = providedName ? *providedName : qname; ids.qtype = QType::A; ids.qclass = QClass::IN; ids.protocol = dnsdist::Protocol::DoUDP; ids.queryRealTime.start(); DNSQuestion dq(ids, packet); return dq; } BOOST_AUTO_TEST_SUITE(dnsdistluarules_cc) BOOST_AUTO_TEST_CASE(test_MaxQPSIPRule) { size_t maxQPS = 10; size_t maxBurst = maxQPS; unsigned int expiration = 300; unsigned int cleanupDelay = 60; unsigned int scanFraction = 10; MaxQPSIPRule rule(maxQPS, maxBurst, 32, 64, expiration, cleanupDelay, scanFraction); InternalQueryState ids; ids.qname = DNSName("powerdns.com."); ids.qtype = QType::A; ids.qclass = QClass::IN; ids.origDest = ComboAddress("127.0.0.1:53"); ids.origRemote = ComboAddress("192.0.2.1:42"); ids.protocol = dnsdist::Protocol::DoUDP; ids.queryRealTime.start(); PacketBuffer packet(sizeof(dnsheader)); struct timespec expiredTime; /* the internal QPS limiter does not use the real time */ gettime(&expiredTime); DNSQuestion dq(ids, packet); for (size_t idx = 0; idx < maxQPS; idx++) { /* let's use different source ports, it shouldn't matter */ ids.origRemote = ComboAddress("192.0.2.1:" + std::to_string(idx)); BOOST_CHECK_EQUAL(rule.matches(&dq), false); BOOST_CHECK_EQUAL(rule.getEntriesCount(), 1U); } /* maxQPS + 1, we should be blocked */ BOOST_CHECK_EQUAL(rule.matches(&dq), true); BOOST_CHECK_EQUAL(rule.getEntriesCount(), 1U); /* remove all entries that have not been updated since 'now' + 1, so all of them */ expiredTime.tv_sec += 1; rule.cleanup(expiredTime); /* we should have been cleaned up */ BOOST_CHECK_EQUAL(rule.getEntriesCount(), 0U); struct timespec beginInsertionTime; gettime(&beginInsertionTime); /* we should not be blocked anymore */ BOOST_CHECK_EQUAL(rule.matches(&dq), false); /* and we be back */ BOOST_CHECK_EQUAL(rule.getEntriesCount(), 1U); /* Let's insert a lot of different sources now */ for (size_t idxByte3 = 0; idxByte3 < 256; idxByte3++) { for (size_t idxByte4 = 0; idxByte4 < 256; idxByte4++) { ids.origRemote = ComboAddress("10.0." + std::to_string(idxByte3) + "." + std::to_string(idxByte4)); BOOST_CHECK_EQUAL(rule.matches(&dq), false); } } struct timespec endInsertionTime; gettime(&endInsertionTime); /* don't forget the existing entry */ size_t total = 1 + 256 * 256; BOOST_CHECK_EQUAL(rule.getEntriesCount(), total); /* make sure all entries are still valid */ struct timespec notExpiredTime = beginInsertionTime; notExpiredTime.tv_sec -= 1; size_t scanned = 0; auto removed = rule.cleanup(notExpiredTime, &scanned); BOOST_CHECK_EQUAL(removed, 0U); /* the first entry should still have been valid, we should not have scanned more */ BOOST_CHECK_EQUAL(scanned, rule.getNumberOfShards()); BOOST_CHECK_EQUAL(rule.getEntriesCount(), total); /* make sure all entries are _not_ valid anymore */ expiredTime = endInsertionTime; expiredTime.tv_sec += 1; removed = rule.cleanup(expiredTime, &scanned); BOOST_CHECK_EQUAL(removed, (total / scanFraction) + 1 + rule.getNumberOfShards()); /* we should not have scanned more than scanFraction */ BOOST_CHECK_EQUAL(scanned, removed); BOOST_CHECK_EQUAL(rule.getEntriesCount(), total - removed); rule.clear(); BOOST_CHECK_EQUAL(rule.getEntriesCount(), 0U); removed = rule.cleanup(expiredTime, &scanned); BOOST_CHECK_EQUAL(removed, 0U); BOOST_CHECK_EQUAL(scanned, 0U); } BOOST_AUTO_TEST_CASE(test_poolOutstandingRule) { auto dq = getDQ(); ServerPool sp{}; auto ds1 = std::make_shared(ComboAddress("192.0.2.1:53")); auto ds2 = std::make_shared(ComboAddress("192.0.2.2:53")); /* increase the outstanding count of both */ ds1->outstanding = 400; ds2->outstanding = 30; sp.addServer(ds1); sp.addServer(ds2); BOOST_CHECK_EQUAL(sp.poolLoad(), 400U + 30U); auto localPool = g_pools.getCopy(); addServerToPool(localPool, "test", ds1); addServerToPool(localPool, "test", ds2); g_pools.setState(localPool); PoolOutstandingRule pOR1("test", 10); BOOST_CHECK_EQUAL(pOR1.matches(&dq), true); PoolOutstandingRule pOR2("test", 1000); BOOST_CHECK_EQUAL(pOR2.matches(&dq), false); } BOOST_AUTO_TEST_CASE(test_payloadSizeRule) { auto dnsQuestion = getDQ(); { PayloadSizeRule rule("equal", dnsQuestion.getData().size()); BOOST_CHECK_EQUAL(rule.matches(&dnsQuestion), true); BOOST_CHECK_EQUAL(rule.toString(), "payload size is equal to " + std::to_string(dnsQuestion.getData().size())); } { PayloadSizeRule rule("equal", dnsQuestion.getData().size() + 1); BOOST_CHECK_EQUAL(rule.matches(&dnsQuestion), false); } { PayloadSizeRule rule("greater", dnsQuestion.getData().size()); BOOST_CHECK_EQUAL(rule.matches(&dnsQuestion), false); BOOST_CHECK_EQUAL(rule.toString(), "payload size is greater than " + std::to_string(dnsQuestion.getData().size())); } { PayloadSizeRule rule("greater", dnsQuestion.getData().size() - 1); BOOST_CHECK_EQUAL(rule.matches(&dnsQuestion), true); } { PayloadSizeRule rule("smaller", dnsQuestion.getData().size()); BOOST_CHECK_EQUAL(rule.matches(&dnsQuestion), false); BOOST_CHECK_EQUAL(rule.toString(), "payload size is smaller than " + std::to_string(dnsQuestion.getData().size())); } { PayloadSizeRule rule("smaller", dnsQuestion.getData().size() + 1); BOOST_CHECK_EQUAL(rule.matches(&dnsQuestion), true); } { PayloadSizeRule rule("greaterOrEqual", dnsQuestion.getData().size()); BOOST_CHECK_EQUAL(rule.matches(&dnsQuestion), true); BOOST_CHECK_EQUAL(rule.toString(), "payload size is equal to or greater than " + std::to_string(dnsQuestion.getData().size())); } { PayloadSizeRule rule("greaterOrEqual", dnsQuestion.getData().size() - 1); BOOST_CHECK_EQUAL(rule.matches(&dnsQuestion), true); } { PayloadSizeRule rule("greaterOrEqual", dnsQuestion.getData().size() + 1); BOOST_CHECK_EQUAL(rule.matches(&dnsQuestion), false); } { PayloadSizeRule rule("smallerOrEqual", dnsQuestion.getData().size()); BOOST_CHECK_EQUAL(rule.matches(&dnsQuestion), true); BOOST_CHECK_EQUAL(rule.toString(), "payload size is equal to or smaller than " + std::to_string(dnsQuestion.getData().size())); } { PayloadSizeRule rule("smallerOrEqual", dnsQuestion.getData().size() + 1); BOOST_CHECK_EQUAL(rule.matches(&dnsQuestion), true); } { PayloadSizeRule rule("smallerOrEqual", dnsQuestion.getData().size() - 1); BOOST_CHECK_EQUAL(rule.matches(&dnsQuestion), false); } BOOST_CHECK_THROW(PayloadSizeRule("invalid", 42U), std::runtime_error); } BOOST_AUTO_TEST_SUITE_END()