const unsigned int AuthPacketCache::s_mincleaninterval, AuthPacketCache::s_maxcleaninterval;
-AuthPacketCache::AuthPacketCache(size_t mapsCount): d_maps(mapsCount), d_lastclean(time(nullptr))
+AuthPacketCache::AuthPacketCache(size_t mapsCount): d_mapscount(mapsCount), d_lastclean(time(nullptr))
{
S.declare("packetcache-hit", "Number of hits on the packet cache");
S.declare("packetcache-miss", "Number of misses on the packet cache");
d_statnumhit=S.getPointer("packetcache-hit");
d_statnummiss=S.getPointer("packetcache-miss");
d_statnumentries=S.getPointer("packetcache-size");
+
+ // Create the MapCombo for the default view
+ std::string defaultview{};
+ createViewMap(defaultview);
+}
+
+// Create the vector<MapCombo> for the given view.
+// Assumes there is no existing data for the view. Callers are expected to
+// know what they are doing.
+std::unordered_map<std::string, std::unique_ptr<vector<AuthPacketCache::MapCombo>>>::iterator AuthPacketCache::createViewMap(const std::string& view)
+{
+ auto iter = d_cache.emplace(view, std::make_unique<vector<MapCombo>>(d_mapscount));
+ auto retval = iter.first;
+ auto* map = retval->second.get();
+ // Note that this reserves more than intended, especially if multiple views
+ // are used.
+ for (auto& shard : *map) {
+ shard.reserve(d_maxEntries / map->size());
+ }
+ return retval;
}
void AuthPacketCache::MapCombo::reserve(size_t numberOfEntries)
#endif /* BOOST_VERSION >= 105600 */
}
-bool AuthPacketCache::get(DNSPacket& p, DNSPacket& cached)
+bool AuthPacketCache::get(DNSPacket& pkt, DNSPacket& cached, const std::string& view)
{
if(!d_ttl) {
return false;
cleanupIfNeeded();
static const std::unordered_set<uint16_t> optionsToSkip{ EDNSOptionCode::COOKIE};
- uint32_t hash = canHashPacket(p.getString(), /* don't skip ECS */optionsToSkip);
- p.setHash(hash);
+ uint32_t hash = canHashPacket(pkt.getString(), /* don't skip ECS */optionsToSkip);
+ pkt.setHash(hash);
string value;
bool haveSomething;
time_t now = time(nullptr);
- auto& mc = getMap(p.qdomain);
+ auto iter = d_cache.find(view);
+ if (iter == d_cache.end()) {
+ // No data for this view yet.
+ (*d_statnummiss)++;
+ return false;
+ }
+ auto& mapcombo = getMap(iter->second, pkt.qdomain);
{
- auto map = mc.d_map.try_read_lock();
+ auto map = mapcombo.d_map.try_read_lock();
if (!map.owns_lock()) {
S.inc("deferred-packetcache-lookup");
return false;
}
- haveSomething = getEntryLocked(*map, p.getString(), hash, p.qdomain, p.qtype.getCode(), p.d_tcp, now, value);
+ haveSomething = AuthPacketCache::getEntryLocked(*map, pkt.getString(), hash, pkt.qdomain, pkt.qtype.getCode(), pkt.d_tcp, now, value);
}
if (!haveSomething) {
}
(*d_statnumhit)++;
- cached.spoofQuestion(p); // for correct case
- cached.qdomain = p.qdomain;
- cached.qtype = p.qtype;
+ cached.spoofQuestion(pkt); // for correct case
+ cached.qdomain = pkt.qdomain;
+ cached.qtype = pkt.qtype;
return true;
}
return iter->tcp == tcp && iter->qtype == qtype && iter->qname == qname && queryMatches(iter->query, query, qname, skippedEDNSTypes);
}
-void AuthPacketCache::insert(DNSPacket& q, DNSPacket& r, unsigned int maxTTL)
+void AuthPacketCache::insert(DNSPacket& query, DNSPacket& response, unsigned int maxTTL, const std::string& view)
{
if(!d_ttl) {
return;
cleanupIfNeeded();
- if (ntohs(q.d.qdcount) != 1) {
+ if (ntohs(query.d.qdcount) != 1) {
return; // do not try to cache packets with multiple questions
}
- if (q.qclass != QClass::IN) // we only cache the INternet
+ if (query.qclass != QClass::IN) { // we only cache the INternet
return;
+ }
uint32_t ourttl = std::min(d_ttl, maxTTL);
if (ourttl == 0) {
return;
- }
+ }
- uint32_t hash = q.getHash();
+ uint32_t hash = query.getHash();
time_t now = time(nullptr);
CacheEntry entry;
entry.hash = hash;
entry.created = now;
entry.ttd = now + ourttl;
- entry.qname = q.qdomain;
- entry.qtype = q.qtype.getCode();
- entry.value = r.getString();
- entry.tcp = r.d_tcp;
- entry.query = q.getString();
-
- auto& mc = getMap(entry.qname);
+ entry.qname = query.qdomain;
+ entry.qtype = query.qtype.getCode();
+ entry.value = response.getString();
+ entry.tcp = response.d_tcp;
+ entry.query = query.getString();
+
+ auto iter = d_cache.find(view);
+ if (iter == d_cache.end()) {
+ // No data for this view yet, create it.
+ iter = createViewMap(view);
+ }
+ auto& mc = getMap(iter->second, entry.qname); // NOLINT(readability-identifier-length)
{
auto map = mc.d_map.try_write_lock();
if (!map.owns_lock()) {
auto& idx = map->get<HashTag>();
auto range = idx.equal_range(hash);
- auto iter = range.first;
+ auto iter2 = range.first;
- for( ; iter != range.second ; ++iter) {
- if (!entryMatches(iter, entry.query, entry.qname, entry.qtype, entry.tcp)) {
+ for( ; iter2 != range.second ; ++iter2) {
+ if (!entryMatches(iter2, entry.query, entry.qname, entry.qtype, entry.tcp)) {
continue;
}
- moveCacheItemToBack<SequencedTag>(*map, iter);
- iter->value = entry.value;
- iter->ttd = now + ourttl;
- iter->created = now;
+ moveCacheItemToBack<SequencedTag>(*map, iter2);
+ iter2->value = entry.value;
+ iter2->ttd = now + ourttl;
+ iter2->created = now;
return;
}
bool AuthPacketCache::getEntryLocked(const cmap_t& map, const std::string& query, uint32_t hash, const DNSName &qname, uint16_t qtype, bool tcp, time_t now, string& value)
{
- auto& idx = map.get<HashTag>();
+ const auto& idx = map.get<HashTag>();
auto range = idx.equal_range(hash);
for(auto iter = range.first; iter != range.second ; ++iter) {
if (!entryMatches(iter, query, qname, qtype, tcp)) {
continue;
}
-
value = iter->value;
return true;
}
d_statnumentries->store(0);
- return purgeLockedCollectionsVector(d_maps);
+ uint64_t delcount = 0;
+ for (auto& iter : d_cache) {
+ auto* map = iter.second.get();
+ delcount += purgeLockedCollectionsVector(*map);
+ }
+ return delcount;
}
uint64_t AuthPacketCache::purgeExact(const DNSName& qname)
{
- auto& mc = getMap(qname);
- uint64_t delcount = purgeExactLockedCollection<NameTag>(mc, qname);
+ uint64_t delcount = 0;
+
+ for (auto& iter : d_cache) {
+ auto& mc = getMap(iter.second, qname); // NOLINT(readability-identifier-length)
+ delcount += purgeExactLockedCollection<NameTag>(mc, qname);
+ }
+
+ *d_statnumentries -= delcount;
+
+ return delcount;
+}
+
+uint64_t AuthPacketCache::purgeExact(const std::string& view, const DNSName& qname)
+{
+ uint64_t delcount = 0;
+
+ if (auto iter = d_cache.find(view); iter != d_cache.end()) {
+ auto& mc = getMap(iter->second, qname); // NOLINT(readability-identifier-length)
+ delcount += purgeExactLockedCollection<NameTag>(mc, qname);
+ }
*d_statnumentries -= delcount;
uint64_t delcount = 0;
if(boost::ends_with(match, "$")) {
- delcount = purgeLockedCollectionsVector<NameTag>(d_maps, match);
+ for (auto& iter : d_cache) {
+ auto* map = iter.second.get();
+ delcount += purgeLockedCollectionsVector<NameTag>(*map, match);
+ }
*d_statnumentries -= delcount;
}
else {
return delcount;
}
-
+
void AuthPacketCache::cleanup()
{
- uint64_t totErased = pruneLockedCollectionsVector<SequencedTag>(d_maps);
+ uint64_t totErased = 0;
+ for (auto& iter : d_cache) {
+ auto* map = iter.second.get();
+ totErased += pruneLockedCollectionsVector<SequencedTag>(*map);
+ }
*d_statnumentries -= totErased;
DLOG(g_log<<"Done with cache clean, cacheSize: "<<(*d_statnumentries)<<", totErased"<<totErased<<endl);
#pragma once
#include <string>
#include <map>
+#include <unordered_map>
#include "dns.hh"
#include <boost/version.hpp>
#include "namespaces.hh"
/** This class performs 'whole packet caching'. Feed it a question packet and it will
try to find an answer. If you have an answer, insert it to have it cached for later use.
Take care not to replace existing cache entries. While this works, it is wasteful. Only
- insert packets that where not found by get()
+ insert packets that were not found by get()
+
+ Caches are indexed by views. When views are not used, all the data in the
+ cache is associated to the empty string "" default view.
Locking!
public:
AuthPacketCache(size_t mapsCount=1024);
- void insert(DNSPacket& q, DNSPacket& r, uint32_t maxTTL); //!< We copy the contents of *p into our cache. Do not needlessly call this to insert questions already in the cache as it wastes resources
+ void insert(DNSPacket& query, DNSPacket& response, uint32_t maxTTL, const std::string& view); //!< We copy the contents of *p into our cache. Do not needlessly call this to insert questions already in the cache as it wastes resources
- bool get(DNSPacket& p, DNSPacket& q); //!< You need to spoof in the right ID with the DNSPacket.spoofID() method.
+ bool get(DNSPacket& pkt, DNSPacket& cached, const std::string& view = ""); //!< You need to spoof in the right ID with the DNSPacket.spoofID() method.
void cleanup(); //!< force the cache to preen itself from expired packets
uint64_t purge();
uint64_t purge(const std::string& match); // could be $ terminated. Is not a dnsname!
uint64_t purgeExact(const DNSName& qname); // no wildcard matching here
+ uint64_t purgeExact(const std::string& view, const DNSName& qname); // same as above, but in the given view
uint64_t size() const { return *d_statnumentries; };
void setMaxEntries(uint64_t maxEntries)
{
d_maxEntries = maxEntries;
- for (auto& shard : d_maps) {
- shard.reserve(maxEntries / d_maps.size());
+ for (auto& iter : d_cache) {
+ auto* map = iter.second.get();
+
+ for (auto& shard : *map) {
+ shard.reserve(maxEntries / map->size());
+ }
}
}
void setTTL(uint32_t ttl)
{
d_ttl = ttl;
}
- bool enabled()
+ bool enabled() const
{
return (d_ttl > 0);
}
SharedLockGuarded<cmap_t> d_map;
};
- vector<MapCombo> d_maps;
- MapCombo& getMap(const DNSName& name)
+ std::unordered_map<std::string, std::unique_ptr<vector<MapCombo>>> d_cache;
+ static MapCombo& getMap(std::unique_ptr<vector<MapCombo>>& map, const DNSName& name)
{
- return d_maps[name.hash() % d_maps.size()];
+ return (*map)[name.hash() % map->size()];
}
+ std::unordered_map<std::string, std::unique_ptr<vector<MapCombo>>>::iterator createViewMap(const std::string& view);
static bool entryMatches(cmap_t::index<HashTag>::type::iterator& iter, const std::string& query, const DNSName& qname, uint16_t qtype, bool tcp);
- bool getEntryLocked(const cmap_t& map, const std::string& query, uint32_t hash, const DNSName &qname, uint16_t qtype, bool tcp, time_t now, string& entry);
+ static bool getEntryLocked(const cmap_t& map, const std::string& query, uint32_t hash, const DNSName &qname, uint16_t qtype, bool tcp, time_t now, string& value);
void cleanupIfNeeded();
AtomicCounter d_ops{0};
AtomicCounter *d_statnumentries;
uint64_t d_maxEntries{0};
+ size_t d_mapscount;
time_t d_lastclean; // doesn't need to be atomic
unsigned long d_nextclean{4096};
unsigned int d_cleaninterval{4096};
#include "statbag.hh"
#include "auth-packetcache.hh"
#include "auth-querycache.hh"
+#ifdef PDNS_AUTH
+#include "auth-zonecache.hh"
+#endif
#include "arguments.hh"
#include <utility>
#include <thread>
QC.insert(a, QType(QType::A), vector<DNSZoneRecord>(records), 3600, 1);
if(!QC.purge(a.toString()))
- BOOST_FAIL("Could not remove entry we just added to the query cache!");
+ BOOST_FAIL("Could not remove entry we just added to the query cache!");
QC.insert(a, QType(QType::A), vector<DNSZoneRecord>(records), 3600, 1);
}
int64_t expected=counter-delcounter;
for(; delcounter < counter; ++delcounter) {
if(QC.getEntry(DNSName("hello ")+DNSName(std::to_string(delcounter)), QType(QType::A), entry, 1)) {
- matches++;
+ matches++;
}
}
BOOST_CHECK_EQUAL(matches, expected);
q.setHash(g_PC->canHashPacket(q.getString()));
const unsigned int maxTTL = 3600;
- g_PC->insert(q, r, maxTTL);
+ g_PC->insert(q, r, maxTTL, "");
}
}
catch(PDNSException& e) {
/* this call is required so the correct hash is set into q->d_hash */
BOOST_CHECK_EQUAL(PC.get(q, r2), false);
- PC.insert(q, r, 3600);
+ PC.insert(q, r, 3600, "");
BOOST_CHECK_EQUAL(PC.size(), 1U);
BOOST_CHECK_EQUAL(PC.get(q, r2), true);
/* with EDNS, should not match */
BOOST_CHECK_EQUAL(PC.get(ednsQ, r2), false);
/* inserting the EDNS-enabled one too */
- PC.insert(ednsQ, r, 3600);
+ PC.insert(ednsQ, r, 3600, "");
BOOST_CHECK_EQUAL(PC.size(), 2U);
/* different EDNS versions, should not match */
/* inserting the version with ECS Client Subnet set,
it should NOT replace the existing EDNS one. */
- PC.insert(ecs1, r, 3600);
+ PC.insert(ecs1, r, 3600, "");
BOOST_CHECK_EQUAL(PC.size(), 3U);
/* different subnet of same size, should NOT match
BOOST_CHECK_EQUAL(PC.get(q, r2), false);
BOOST_CHECK_EQUAL(PC.size(), 0U);
- PC.insert(q, r, 3600);
+ PC.insert(q, r, 3600, "");
BOOST_CHECK_EQUAL(PC.size(), 1U);
BOOST_CHECK_EQUAL(PC.get(q, r2), true);
BOOST_CHECK_EQUAL(r2.qdomain, r.qdomain);
BOOST_CHECK_EQUAL(PC.get(q, r2), false);
BOOST_CHECK_EQUAL(PC.size(), 0U);
- PC.insert(q, r, 3600);
+ PC.insert(q, r, 3600, "");
BOOST_CHECK_EQUAL(PC.size(), 1U);
BOOST_CHECK_EQUAL(PC.get(q, r2), true);
BOOST_CHECK_EQUAL(r2.qdomain, r.qdomain);
BOOST_CHECK_EQUAL(PC.get(q, r2), false);
BOOST_CHECK_EQUAL(PC.size(), 0U);
- PC.insert(q, r, 3600);
+ PC.insert(q, r, 3600, "");
BOOST_CHECK_EQUAL(PC.size(), 1U);
BOOST_CHECK_EQUAL(PC.get(q, r2), true);
BOOST_CHECK_EQUAL(r2.qdomain, r.qdomain);
BOOST_CHECK_EQUAL(PC.get(q, r2), false);
BOOST_CHECK_EQUAL(PC.size(), 0U);
- PC.insert(q, r, 3600);
+ PC.insert(q, r, 3600, "");
BOOST_CHECK_EQUAL(PC.size(), 1U);
BOOST_CHECK_EQUAL(PC.purge("www.powerdns.net"), 0U);
BOOST_CHECK_EQUAL(PC.get(q, r2), true);
}
}
+static void feedPacketCache(AuthPacketCache& PC, uint32_t bits, const std::string& view) // NOLINT(readability-identifier-length)
+{
+ for (unsigned int counter = 0; counter < 128; ++counter) {
+ std::vector<uint8_t> storage;
+ DNSName qname = DNSName("network" + std::to_string(counter));
+ DNSPacketWriter qwriter(storage, qname, QType::A);
+ DNSPacket query(true);
+ query.parse(reinterpret_cast<char*>(storage.data()), storage.size()); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast): can't static_cast because of sign difference
+ storage.clear();
+ DNSPacketWriter rwriter(storage, qname, QType::A);
+ rwriter.startRecord(qname, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER);
+ rwriter.xfrIP(htonl((counter << 24) | bits));
+ rwriter.commit();
+ DNSPacket response(false);
+ response.parse(reinterpret_cast<char*>(storage.data()), storage.size()); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast): can't static_cast because of sign difference
+ // magic copied from threadPCMangler() above
+ query.setHash(PacketCache::canHashPacket(query.getString()));
+ PC.insert(query, response, 2600, view);
+ }
+}
+
+static void slurpPacketCache(AuthPacketCache& PC, const std::string& bits, const std::string& view) // NOLINT(readability-identifier-length)
+{
+ for (unsigned int counter = 0; counter < 128; ++counter) {
+ std::vector<uint8_t> storage;
+ DNSName qname = DNSName("network" + std::to_string(counter));
+ DNSPacketWriter qwriter(storage, qname, QType::A);
+ DNSPacket query(true);
+ query.parse(reinterpret_cast<char*>(storage.data()), storage.size()); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast): can't static_cast because of sign difference
+
+ DNSPacket response(false);
+ bool hit = PC.get(query, response, view);
+ BOOST_CHECK_EQUAL(hit, true);
+ if (!hit) {
+ continue;
+ }
+ BOOST_CHECK_EQUAL(response.qdomain, query.qdomain);
+ const std::string& wiresponse = response.getString();
+ MOADNSParser parser(false, wiresponse.c_str(), wiresponse.size());
+ BOOST_REQUIRE_EQUAL(parser.d_answers.size(), 1U);
+ const auto& record = parser.d_answers.at(0);
+ BOOST_REQUIRE_EQUAL(record.d_type, QType::A);
+ BOOST_REQUIRE_EQUAL(record.d_class, QClass::IN);
+ auto content = getRR<ARecordContent>(record);
+ BOOST_REQUIRE(content);
+ BOOST_REQUIRE_EQUAL(content->getCA().toString(), std::to_string(counter) + bits);
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_AuthPacketCacheNetmasks) {
+ try {
+ ::arg().setSwitch("no-shuffle","Set this to prevent random shuffling of answers - for regression testing")="off";
+
+ AuthPacketCache PC; // NOLINT(readability-identifier-length)
+ PC.setMaxEntries(1000000);
+ PC.setTTL(0xc0ffee); // cache works better when programmer is cafeinated and doesn't forget to enable it
+
+ std::string view1{"view1"};
+ std::string view2{"view2"};
+
+ // Set up a few packets with no view.
+ feedPacketCache(PC, 0x00010203, "");
+ BOOST_REQUIRE_EQUAL(PC.size(), 128 * 1);
+
+ // Set up a few packets with a view and different A result.
+ feedPacketCache(PC, 0x00020406, view1);
+ BOOST_REQUIRE_EQUAL(PC.size(), 128 * 2);
+
+ // Set up a few packets with yet another view and yet another different A result.
+ feedPacketCache(PC, 0x00030609, view2);
+ BOOST_REQUIRE_EQUAL(PC.size(), 128 * 3);
+
+ // Now check that we are getting cache hits for all the packets we've added,
+ // with the correct answers
+ slurpPacketCache(PC, ".1.2.3", "");
+ slurpPacketCache(PC, ".2.4.6", view1);
+ slurpPacketCache(PC, ".3.6.9", view2);
+ }
+ catch(PDNSException& e) {
+ cerr<<"Had error in AuthPacketCache: "<<e.reason<<endl;
+ throw;
+ }
+}
+
+#ifdef PDNS_AUTH // [
+// Combined packet cache and zone cache test to exercize views
+
+static DNSPacket buildQuery(const DNSName& qname)
+{
+ std::vector<uint8_t> storage;
+ DNSPacketWriter qwriter(storage, qname, QType::A);
+ DNSPacket query(true);
+ query.parse(reinterpret_cast<char*>(storage.data()), storage.size()); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast): can't static_cast because of sign difference
+ storage.clear();
+ // magic copied from threadPCMangler() above
+ query.setHash(PacketCache::canHashPacket(query.getString()));
+ return query;
+}
+
+static void feedPacketCache2(AuthPacketCache& PC, const std::string& view, uint32_t ipAddress, const DNSName& qname) // NOLINT(readability-identifier-length)
+{
+ DNSPacket query = buildQuery(qname);
+
+ std::vector<uint8_t> storage;
+ DNSPacketWriter rwriter(storage, qname, QType::A);
+ rwriter.startRecord(qname, QType::A, 3600, QClass::IN, DNSResourceRecord::ANSWER);
+ rwriter.xfrIP(htonl(ipAddress));
+ rwriter.commit();
+ DNSPacket response(false);
+ response.parse(reinterpret_cast<char*>(storage.data()), storage.size()); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast): can't static_cast because of sign difference
+
+ PC.insert(query, response, 2600, view);
+}
+
+static bool queryPacketCache2(AuthPacketCache& PC, AuthZoneCache& ZC, ComboAddress from, const DNSName& qname, const Netmask& expectedMask, const std::string& expectedView, const std::string& expectedAddress) // NOLINT(readability-identifier-length)
+{
+ DNSPacket query = buildQuery(qname);
+ DNSPacket response(false);
+
+ Netmask netmask(from);
+ std::string view = ZC.getViewFromNetwork(&netmask);
+ BOOST_REQUIRE(netmask == expectedMask);
+ BOOST_REQUIRE(view == expectedView);
+
+ bool hit = PC.get(query, response, view);
+ if (hit) {
+ BOOST_CHECK_EQUAL(response.qdomain, query.qdomain);
+ const std::string& wiresponse = response.getString();
+ MOADNSParser parser(false, wiresponse.c_str(), wiresponse.size());
+ BOOST_REQUIRE_EQUAL(parser.d_answers.size(), 1U);
+ const auto& record = parser.d_answers.at(0);
+ BOOST_REQUIRE_EQUAL(record.d_type, QType::A);
+ BOOST_REQUIRE_EQUAL(record.d_class, QClass::IN);
+ auto content = getRR<ARecordContent>(record);
+ BOOST_REQUIRE(content);
+ BOOST_REQUIRE_EQUAL(content->getCA().toString(), expectedAddress);
+ }
+ return hit;
+}
+
+BOOST_AUTO_TEST_CASE(test_AuthViews)
+{
+ // Setup Zone Cache
+
+ AuthZoneCache ZC; // NOLINT(readability-identifier-length)
+ ZC.setRefreshInterval(3600);
+
+ // Declare a few zones
+ ZoneName foo("example.com..foo");
+ ZoneName bar("example.com..bar");
+ ZC.add(foo, static_cast<domainid_t>('F'));
+ ZC.add(bar, static_cast<domainid_t>('B'));
+
+ // Declare a few networks
+ std::string view1{"view1"};
+ std::string view2{"view2"};
+ Netmask outerMask("192.0.2.0/24");
+ Netmask innerMask("192.0.2.0/25");
+ ZC.updateNetwork(outerMask, view1);
+ ZC.updateNetwork(innerMask, view2);
+
+ // Declare a few views
+ ZC.addToView(view1, foo);
+ ZC.addToView(view2, bar);
+
+ // Setup Packet Cache
+
+ AuthPacketCache PC; // NOLINT(readability-identifier-length)
+ PC.setMaxEntries(1000000);
+ PC.setTTL(0xc0ffee); // cache works better when programmer is cafeinated and doesn't forget to enable it
+
+ // Cache answer for query in view2
+ DNSName qname("example.com");
+ feedPacketCache2(PC, view2, 0x02020202, qname);
+ BOOST_CHECK_EQUAL(PC.size(), 1);
+
+ // Check that requesting from view1 causes a cache miss
+ BOOST_CHECK_EQUAL(queryPacketCache2(PC, ZC, ComboAddress("192.0.2.128"), qname, outerMask, view1, "1.1.1.1"), false);
+
+ // Check that requesting from view2 causes a cache hit
+ BOOST_CHECK_EQUAL(queryPacketCache2(PC, ZC, ComboAddress("192.0.2.1"), qname, innerMask, view2, "2.2.2.2"), true);
+
+ // Cache answer for query in view1
+ feedPacketCache2(PC, view1, 0x01010101, qname);
+ BOOST_CHECK_EQUAL(PC.size(), 2);
+
+ // Check that requesting from view1 causes a cache hit with the right data
+ BOOST_CHECK_EQUAL(queryPacketCache2(PC, ZC, ComboAddress("192.0.2.128"), qname, outerMask, view1, "1.1.1.1"), true);
+
+ // Check that requesting from view2 causes a cache hit with the right data
+ BOOST_CHECK_EQUAL(queryPacketCache2(PC, ZC, ComboAddress("192.0.2.1"), qname, innerMask, view2, "2.2.2.2"), true);
+
+ // Purge view2
+ BOOST_CHECK_EQUAL(PC.purgeExact(view2, qname), 1);
+ BOOST_CHECK_EQUAL(PC.size(), 1);
+
+ // Check that requesting from view2 causes a cache miss
+ BOOST_CHECK_EQUAL(queryPacketCache2(PC, ZC, ComboAddress("192.0.2.1"), qname, innerMask, view2, "2.2.2.2"), false);
+
+ // Check that requesting from view1 causes a cache hit with the right data
+ BOOST_CHECK_EQUAL(queryPacketCache2(PC, ZC, ComboAddress("192.0.2.128"), qname, outerMask, view1, "1.1.1.1"), true);
+
+ // Purge view1
+ BOOST_CHECK_EQUAL(PC.purgeExact(view1, qname), 1);
+ BOOST_CHECK_EQUAL(PC.size(), 0);
+
+ // Check that requesting from view1 causes a cache miss
+ BOOST_CHECK_EQUAL(queryPacketCache2(PC, ZC, ComboAddress("192.0.2.128"), qname, outerMask, view1, "1.1.1.1"), false);
+
+ // Cache answers for view1 and view2 again
+ feedPacketCache2(PC, view1, 0x01010101, qname);
+ feedPacketCache2(PC, view2, 0x02020202, qname);
+ BOOST_CHECK_EQUAL(PC.size(), 2);
+
+ // Purge all views
+ BOOST_CHECK_EQUAL(PC.purgeExact(qname), 2);
+ BOOST_CHECK_EQUAL(PC.size(), 0);
+
+ // Check that requesting from view1 causes a cache miss
+ BOOST_CHECK_EQUAL(queryPacketCache2(PC, ZC, ComboAddress("192.0.2.128"), qname, outerMask, view1, "1.1.1.1"), false);
+
+ // Check that requesting from view2 causes a cache miss
+ BOOST_CHECK_EQUAL(queryPacketCache2(PC, ZC, ComboAddress("192.0.2.1"), qname, innerMask, view2, "2.2.2.2"), false);
+}
+#endif // ] PDNS_AUTH
+
BOOST_AUTO_TEST_SUITE_END()