From: Remi Gacogne Date: Mon, 15 May 2017 15:59:12 +0000 (+0200) Subject: rec: Add unit tests for the `MemRecursorCache` class X-Git-Tag: rec-4.1.0-alpha1~118^2 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=refs%2Fpull%2F5319%2Fhead;p=thirdparty%2Fpdns.git rec: Add unit tests for the `MemRecursorCache` class --- diff --git a/pdns/recursor_cache.hh b/pdns/recursor_cache.hh index 48c1803f29..baa988b3e2 100644 --- a/pdns/recursor_cache.hh +++ b/pdns/recursor_cache.hh @@ -57,12 +57,12 @@ public: void replace(time_t, const DNSName &qname, const QType& qt, const vector& content, const vector>& signatures, bool auth, boost::optional ednsmask=boost::optional()); void doPrune(void); - void doSlash(int perc); uint64_t doDump(int fd); uint64_t doDumpNSSpeeds(int fd); int doWipeCache(const DNSName& name, bool sub, uint16_t qtype=0xffff); bool doAgeCache(time_t now, const DNSName& name, uint16_t qtype, uint32_t newTTL); + uint64_t cacheHits, cacheMisses; private: diff --git a/pdns/recursordist/Makefile.am b/pdns/recursordist/Makefile.am index 03694b1cb5..d4e3fee05a 100644 --- a/pdns/recursordist/Makefile.am +++ b/pdns/recursordist/Makefile.am @@ -230,6 +230,7 @@ testrunner_SOURCES = \ test-negcache_cc.cc \ test-rcpgenerator_cc.cc \ test-recpacketcache_cc.cc \ + test-recursorcache_cc.cc \ test-syncres_cc.cc \ test-tsig.cc \ testrunner.cc \ diff --git a/pdns/recursordist/test-recursorcache_cc.cc b/pdns/recursordist/test-recursorcache_cc.cc new file mode 100644 index 0000000000..1974bbea8a --- /dev/null +++ b/pdns/recursordist/test-recursorcache_cc.cc @@ -0,0 +1,266 @@ +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_NO_MAIN + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include +#include + +#include "iputils.hh" +#include "recursor_cache.hh" + +BOOST_AUTO_TEST_SUITE(recursorcache_cc) + +BOOST_AUTO_TEST_CASE(test_RecursorCacheSimple) { + MemRecursorCache MRC; + + std::vector records; + std::vector> signatures; + time_t now = time(nullptr); + + BOOST_CHECK_EQUAL(MRC.size(), 0); + MRC.replace(now, DNSName("hello"), QType(QType::A), records, signatures, true, boost::none); + BOOST_CHECK_EQUAL(MRC.size(), 1); + BOOST_CHECK_GT(MRC.bytes(), 1); + BOOST_CHECK_EQUAL(MRC.doWipeCache(DNSName("hello"), false, QType::A), 1); + BOOST_CHECK_EQUAL(MRC.size(), 0); + BOOST_CHECK_EQUAL(MRC.bytes(), 0); + + uint64_t counter = 0; + try { + for(counter = 0; counter < 100000; ++counter) { + DNSName a = DNSName("hello ")+DNSName(std::to_string(counter)); + BOOST_CHECK_EQUAL(DNSName(a.toString()), a); + + MRC.replace(now, a, QType(QType::A), records, signatures, true, boost::none); + if(!MRC.doWipeCache(a, false)) + BOOST_FAIL("Could not remove entry we just added to the cache!"); + MRC.replace(now, a, QType(QType::A), records, signatures, true, boost::none); + } + + BOOST_CHECK_EQUAL(MRC.size(), counter); + + uint64_t delcounter = 0; + for(delcounter=0; delcounter < counter/100; ++delcounter) { + DNSName a = DNSName("hello ")+DNSName(std::to_string(delcounter)); + BOOST_CHECK_EQUAL(MRC.doWipeCache(a, false, QType::A), 1); + } + + BOOST_CHECK_EQUAL(MRC.size(), counter-delcounter); + + std::vector retrieved; + ComboAddress who("192.0.2.1"); + uint64_t matches = 0; + int64_t expected = counter-delcounter; + + for(; delcounter < counter; ++delcounter) { + if(MRC.get(now, DNSName("hello ")+DNSName(std::to_string(delcounter)), QType(QType::A), &retrieved, who, nullptr)) { + matches++; + } + } + BOOST_CHECK_EQUAL(matches, expected); + BOOST_CHECK_EQUAL(retrieved.size(), records.size()); + + MRC.doWipeCache(DNSName("."), true); + BOOST_CHECK_EQUAL(MRC.size(), 0); + + time_t ttd = now + 30; + DNSName power("powerdns.com."); + DNSRecord dr1; + ComboAddress dr1Content("2001:DB8::1"); + dr1.d_name = power; + dr1.d_type = QType::AAAA; + dr1.d_class = QClass::IN; + dr1.d_content = std::make_shared(dr1Content); + dr1.d_ttl = static_cast(ttd); + dr1.d_place = DNSResourceRecord::ANSWER; + + DNSRecord dr2; + ComboAddress dr2Content("192.0.2.42"); + dr2.d_name = power; + dr2.d_type = QType::A; + dr2.d_class = QClass::IN; + dr2.d_content = std::make_shared(dr2Content); + dr2.d_ttl = static_cast(ttd); + // the place should not matter to the cache + dr2.d_place = DNSResourceRecord::AUTHORITY; + + // insert a subnet specific entry + records.push_back(dr1); + MRC.replace(now, power, QType(QType::AAAA), records, signatures, true, boost::optional("192.0.2.1/25")); + BOOST_CHECK_EQUAL(MRC.size(), 1); + + retrieved.clear(); + // subnet specific should be returned for a matching subnet + BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::AAAA), &retrieved, ComboAddress("192.0.2.2"), nullptr), (ttd-now)); + BOOST_REQUIRE_EQUAL(retrieved.size(), 1); + BOOST_CHECK_EQUAL(getRR(retrieved.at(0))->getCA().toString(), dr1Content.toString()); + + retrieved.clear(); + // subnet specific should not be returned for a different subnet + BOOST_CHECK_LT(MRC.get(now, power, QType(QType::AAAA), &retrieved, ComboAddress("127.0.0.1"), nullptr), 0); + BOOST_CHECK_EQUAL(retrieved.size(), 0); + + // remove everything + MRC.doWipeCache(DNSName("."), true); + BOOST_CHECK_EQUAL(MRC.size(), 0); + + // insert a NON-subnet specific entry + records.clear(); + records.push_back(dr2); + MRC.replace(now, power, QType(QType::A), records, signatures, true, boost::none); + BOOST_CHECK_EQUAL(MRC.size(), 1); + + // NON-subnet specific should always be returned + BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), &retrieved, ComboAddress("127.0.0.1"), nullptr), (ttd-now)); + BOOST_REQUIRE_EQUAL(retrieved.size(), 1); + BOOST_CHECK_EQUAL(getRR(retrieved.at(0))->getCA().toString(), dr2Content.toString()); + retrieved.clear(); + + // insert a subnet specific entry for the same name but a different QType + records.clear(); + records.push_back(dr1); + MRC.replace(now, power, QType(QType::AAAA), records, signatures, true, boost::optional("192.0.2.1/25")); + // we should not have replaced the existing entry + BOOST_CHECK_EQUAL(MRC.size(), 2); + + // insert a TXT one, we will use that later + records.clear(); + records.push_back(dr1); + MRC.replace(now, power, QType(QType::TXT), records, signatures, true, boost::none); + // we should not have replaced any existing entry + BOOST_CHECK_EQUAL(MRC.size(), 3); + + // we should still get the NON-subnet specific entry + BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), &retrieved, ComboAddress("127.0.0.1"), nullptr), (ttd-now)); + BOOST_REQUIRE_EQUAL(retrieved.size(), 1); + BOOST_CHECK_EQUAL(getRR(retrieved.at(0))->getCA().toString(), dr2Content.toString()); + retrieved.clear(); + + // we should get the subnet specific entry if we are from the right subnet + BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::AAAA), &retrieved, ComboAddress("192.0.2.3"), nullptr), (ttd-now)); + BOOST_REQUIRE_EQUAL(retrieved.size(), 1); + BOOST_CHECK_EQUAL(getRR(retrieved.at(0))->getCA().toString(), dr1Content.toString()); + retrieved.clear(); + + // but nothing from a different subnet + BOOST_CHECK_LT(MRC.get(now, power, QType(QType::AAAA), &retrieved, ComboAddress("127.0.0.1"), nullptr), 0); + BOOST_CHECK_EQUAL(retrieved.size(), 0); + retrieved.clear(); + + // QType::ANY should return any qtype, so from the right subnet we should get all of them + BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::ANY), &retrieved, ComboAddress("192.0.2.3"), nullptr), (ttd-now)); + BOOST_CHECK_EQUAL(retrieved.size(), 3); + for (const auto& rec : retrieved) { + BOOST_CHECK(rec.d_type == QType::A || rec.d_type == QType::AAAA || rec.d_type == QType::TXT); + } + // check that the place is always set to ANSWER + for (const auto& rec : retrieved) { + BOOST_CHECK(rec.d_place == DNSResourceRecord::ANSWER); + } + retrieved.clear(); + + // but only the non-subnet specific from the another subnet + BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::ANY), &retrieved, ComboAddress("127.0.0.1"), nullptr), (ttd-now)); + BOOST_CHECK_EQUAL(retrieved.size(), 2); + for (const auto& rec : retrieved) { + BOOST_CHECK(rec.d_type == QType::A || rec.d_type == QType::TXT); + } + retrieved.clear(); + + // QType::ADDR should return both A and AAAA but no TXT, so two entries from the right subnet + BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::ADDR), &retrieved, ComboAddress("192.0.2.3"), nullptr), (ttd-now)); + BOOST_CHECK_EQUAL(retrieved.size(), 2); + for (const auto& rec : retrieved) { + BOOST_CHECK(rec.d_type == QType::A || rec.d_type == QType::AAAA); + } + retrieved.clear(); + + // but only the non-subnet specific one from the another subnet + BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::ADDR), &retrieved, ComboAddress("127.0.0.1"), nullptr), (ttd-now)); + BOOST_REQUIRE_EQUAL(retrieved.size(), 1); + BOOST_CHECK(retrieved.at(0).d_type == QType::A); + retrieved.clear(); + + // entries are only valid until ttd, we should not get anything after that because they are expired + BOOST_CHECK_LT(MRC.get(ttd + 5, power, QType(QType::ADDR), &retrieved, ComboAddress("127.0.0.1"), nullptr), 0); + BOOST_CHECK_EQUAL(retrieved.size(), 0); + retrieved.clear(); + + // let's age the records for our existing QType::TXT entry so they are now only valid for 5s + uint32_t newTTL = 5; + BOOST_CHECK_EQUAL(MRC.doAgeCache(now, power, QType::TXT, newTTL), true); + + // we should still be able to retrieve it + BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::TXT), &retrieved, ComboAddress("127.0.0.1"), nullptr), newTTL); + BOOST_CHECK_EQUAL(retrieved.size(), 1); + BOOST_CHECK(retrieved.at(0).d_type == QType::TXT); + // please note that this is still a TTD at this point + BOOST_CHECK_EQUAL(retrieved.at(0).d_ttl, now + newTTL); + retrieved.clear(); + + // but 10s later it should be gone + BOOST_CHECK_LT(MRC.get(now + 10, power, QType(QType::TXT), &retrieved, ComboAddress("127.0.0.1"), nullptr), 0); + BOOST_CHECK_EQUAL(retrieved.size(), 0); + retrieved.clear(); + + // wipe everything + MRC.doWipeCache(DNSName("."), true); + BOOST_CHECK_EQUAL(MRC.size(), 0); + records.clear(); + + // insert auth record + records.push_back(dr2); + MRC.replace(now, power, QType(QType::A), records, signatures, true, boost::none); + BOOST_CHECK_EQUAL(MRC.size(), 1); + BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), &retrieved, ComboAddress("127.0.0.1"), nullptr), (ttd-now)); + BOOST_CHECK_EQUAL(retrieved.size(), 1); + + DNSRecord dr3; + ComboAddress dr3Content("192.0.2.84"); + dr3.d_name = power; + dr3.d_type = QType::A; + dr3.d_class = QClass::IN; + dr3.d_content = std::make_shared(dr3Content); + dr3.d_ttl = static_cast(ttd + 100); + // the place should not matter to the cache + dr3.d_place = DNSResourceRecord::AUTHORITY; + + // this is important for our tests + BOOST_REQUIRE_GT(dr3.d_ttl, ttd); + + records.clear(); + records.push_back(dr3); + + // non-auth should not replace valid auth + MRC.replace(now, power, QType(QType::A), records, signatures, false, boost::none); + BOOST_CHECK_EQUAL(MRC.size(), 1); + BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), &retrieved, ComboAddress("127.0.0.1"), nullptr), (ttd-now)); + BOOST_REQUIRE_EQUAL(retrieved.size(), 1); + BOOST_CHECK_EQUAL(getRR(retrieved.at(0))->getCA().toString(), dr2Content.toString()); + + // but non-auth _should_ replace expired auth + MRC.replace(ttd + 1, power, QType(QType::A), records, signatures, false, boost::none); + BOOST_CHECK_EQUAL(MRC.size(), 1); + BOOST_CHECK_EQUAL(MRC.get(ttd + 1, power, QType(QType::A), &retrieved, ComboAddress("127.0.0.1"), nullptr), (dr3.d_ttl - (ttd + 1))); + BOOST_REQUIRE_EQUAL(retrieved.size(), 1); + BOOST_CHECK_EQUAL(getRR(retrieved.at(0))->getCA().toString(), dr3Content.toString()); + + // auth should replace non-auth + records.clear(); + records.push_back(dr2); + MRC.replace(now, power, QType(QType::A), records, signatures, false, boost::none); + BOOST_CHECK_EQUAL(MRC.size(), 1); + BOOST_CHECK_EQUAL(MRC.get(now, power, QType(QType::A), &retrieved, ComboAddress("127.0.0.1"), nullptr), (ttd-now)); + BOOST_REQUIRE_EQUAL(retrieved.size(), 1); + BOOST_CHECK_EQUAL(getRR(retrieved.at(0))->getCA().toString(), dr2Content.toString()); + + } + catch(const PDNSException& e) { + cerr<<"Had error: "<