#include "config.h"
#endif
+#include "pdns/misc.hh"
#include "auth-zonecache.hh"
#include "logger.hh"
#include "statbag.hh"
d_statnumentries = S.getPointer("zone-cache-size");
}
-bool AuthZoneCache::getEntry(const ZoneName& zone, int& zoneId)
+bool AuthZoneCache::getEntry(const ZoneName& zone, domainid_t& zoneId)
{
auto& mc = getMap(zone);
bool found = false;
return found;
}
+#if defined(PDNS_AUTH) // [
+std::string AuthZoneCache::getViewFromNetwork(Netmask* net)
+{
+ string view{};
+
+ if (net == nullptr || net->empty()) {
+ return view;
+ }
+
+ try {
+ auto nets = d_nets.read_lock();
+ const auto* netview = nets->lookup(net->getNetwork());
+ if (netview != nullptr) {
+ // Tell our caller the span of the network being hit...
+ *net = netview->first;
+ // ...and which view it covers.
+ view = netview->second;
+ }
+ }
+ catch (...) {
+ // this handles the "empty" case, but might hide other errors
+ }
+
+ // If this network doesn't match a view, then we want to clear the netmask
+ // information, as our caller might submit it to the packet cache and there
+ // is no reason to narrow caching for views-agnostic queries.
+ // TODO: no longer needed once packet cache indexes on views rather than
+ // netmasks.
+ if (view.empty()) {
+ *net = Netmask();
+ }
+
+ return view;
+}
+
+std::string AuthZoneCache::getVariantFromView(const ZoneName& zone, const std::string& view)
+{
+ string variant{};
+
+ if (!view.empty()) {
+ auto views = d_views.read_lock();
+ if (views->count(view) == 1) {
+ const auto& viewmap = views->at(view);
+ if (viewmap.count(zone.operator const DNSName&()) == 1) {
+ variant = viewmap.at(zone.operator const DNSName&());
+ }
+ }
+ }
+
+ return variant;
+}
+
+void AuthZoneCache::setZoneVariant(std::unique_ptr<DNSPacket>& packet)
+{
+ Netmask net = packet->getRealRemote();
+ string view = getViewFromNetwork(&net);
+ packet->qdomainzone = ZoneName(packet->qdomain);
+ string variant = getVariantFromView(packet->qdomainzone, view);
+ packet->qdomainzone.setVariant(variant);
+}
+#endif // ] PDNS_AUTH
+
bool AuthZoneCache::isEnabled() const
{
return d_refreshinterval > 0;
void AuthZoneCache::clear()
{
purgeLockedCollectionsVector(d_maps);
+ {
+ d_nets.write_lock()->clear();
+ }
+ {
+ d_views.write_lock()->clear();
+ }
}
void AuthZoneCache::replace(const vector<std::tuple<ZoneName, int>>& zone_indices)
}
}
+void AuthZoneCache::replace(NetmaskTree<string> nettree)
+{
+ auto nets = d_nets.write_lock();
+ nets->swap(nettree);
+}
+
+void AuthZoneCache::replace(ViewsMap viewsmap)
+{
+ auto views = d_views.write_lock();
+ views->swap(viewsmap);
+}
+
void AuthZoneCache::add(const ZoneName& zone, const int zoneId)
{
if (!d_refreshinterval)
pending->d_pendingUpdates.clear();
}
}
+
+void AuthZoneCache::addToView(const std::string& view, const ZoneName& zone)
+{
+ const DNSName& strictZone = zone.operator const DNSName&();
+ auto views = d_views.write_lock();
+ AuthZoneCache::ViewsMap& map = *views;
+ map[view][strictZone] = zone.getVariant();
+}
+
+void AuthZoneCache::removeFromView(const std::string& view, const ZoneName& zone)
+{
+ const DNSName& strictZone = zone.operator const DNSName&();
+ auto views = d_views.write_lock();
+ AuthZoneCache::ViewsMap& map = *views;
+ if (map.count(view) == 0) {
+ return; // Nothing to do, we did not know about that view
+ }
+ auto& innerMap = map.at(view);
+ if (auto iter = innerMap.find(strictZone); iter != innerMap.end()) {
+ innerMap.erase(iter);
+ }
+ // else nothing to do, we did not know about that zone in that view
+}
+
+void AuthZoneCache::updateNetwork(const Netmask& network, const std::string& view)
+{
+ auto nets = d_nets.write_lock();
+ if (view.empty()) {
+ nets->erase(network);
+ }
+ else {
+ nets->insert_or_assign(network, view);
+ }
+}
#include "dnsname.hh"
#include "lock.hh"
#include "misc.hh"
+#include "iputils.hh"
class AuthZoneCache : public boost::noncopyable
{
public:
AuthZoneCache(size_t mapsCount = 1024);
+ using ViewsMap = std::map<std::string, std::map<DNSName, std::string>>;
+
+ // Zone maintainance
void replace(const vector<std::tuple<ZoneName, int>>& zone);
+ void replace(NetmaskTree<string> nettree);
+ void replace(ViewsMap viewsmap);
void add(const ZoneName& zone, const int zoneId);
void remove(const ZoneName& zone);
void setReplacePending(); //!< call this when data collection for the subsequent replace() call starts.
- bool getEntry(const ZoneName& zone, int& zoneId);
+ // Views maintainance
+ void addToView(const std::string& view, const ZoneName& zone);
+ void removeFromView(const std::string& view, const ZoneName& zone);
+
+ // Network maintainance
+ void updateNetwork(const Netmask& network, const std::string& view);
+
+ // Zone lookup
+ bool getEntry(const ZoneName& zone, domainid_t& zoneId);
+
+ // View lookup
+ std::string getViewFromNetwork(Netmask* net);
+
+ // Variant lookup
+ std::string getVariantFromView(const ZoneName& zone, const std::string& view);
+ void setZoneVariant(std::unique_ptr<DNSPacket>& packet);
size_t size() { return *d_statnumentries; } //!< number of entries in the cache
void clear();
private:
+ SharedLockGuarded<NetmaskTree<string>> d_nets;
+ SharedLockGuarded<ViewsMap> d_views;
+
struct CacheValue
{
int zoneId{-1};
{
return std::tie(d_network, d_bits) == std::tie(rhs.d_network, rhs.d_bits);
}
+ bool operator!=(const Netmask& rhs) const
+ {
+ return !operator==(rhs);
+ }
[[nodiscard]] bool empty() const
{
return true;
}
- if(!B.getAuth(ZoneName(state.target), pkt.qtype, &d_sd)) {
+ if(!B.getAuth(ZoneName(state.target), pkt.qtype, &d_sd, true, &pkt)) {
DLOG(g_log<<Logger::Error<<"We have no authority over zone '"<<state.target<<"'"<<endl);
if (!retargeted) {
state.r->setA(false); // drop AA if we never had a SOA in the first place
AuthZoneCache cache;
cache.setRefreshInterval(3600);
- vector<std::tuple<ZoneName, int>> zone_indices{
+ vector<std::tuple<ZoneName, domainid_t>> zone_indices{
{ZoneName("example.org."), 1},
};
cache.setReplacePending();
cache.replace(zone_indices);
- int zoneId = 0;
- bool found = cache.getEntry(ZoneName("example.org."), zoneId);
+ domainid_t zoneId = 0;
+ ZoneName zone("example.org");
+ bool found = cache.getEntry(zone, zoneId);
if (!found || zoneId != 1) {
BOOST_FAIL("zone added in replace() not found");
}
AuthZoneCache cache;
cache.setRefreshInterval(3600);
- vector<std::tuple<ZoneName, int>> zone_indices{
+ vector<std::tuple<ZoneName, domainid_t>> zone_indices{
{ZoneName("powerdns.org."), 1}};
cache.setReplacePending();
cache.add(ZoneName("example.org."), 2);
cache.replace(zone_indices);
- int zoneId = 0;
- bool found = cache.getEntry(ZoneName("example.org."), zoneId);
+ domainid_t zoneId = 0;
+ ZoneName zone("example.org");
+ bool found = cache.getEntry(zone, zoneId);
if (!found || zoneId != 2) {
BOOST_FAIL("zone added while replace was pending not found");
}
AuthZoneCache cache;
cache.setRefreshInterval(3600);
- vector<std::tuple<ZoneName, int>> zone_indices{
+ vector<std::tuple<ZoneName, domainid_t>> zone_indices{
{ZoneName("powerdns.org."), 1}};
cache.setReplacePending();
cache.remove(ZoneName("powerdns.org."));
cache.replace(zone_indices);
- int zoneId = 0;
- bool found = cache.getEntry(ZoneName("example.org."), zoneId);
+ domainid_t zoneId = 0;
+ ZoneName zone("example.org");
+ bool found = cache.getEntry(zone, zoneId);
if (found) {
BOOST_FAIL("zone removed while replace was pending is found");
}
AuthZoneCache cache;
cache.setRefreshInterval(3600);
- vector<std::tuple<ZoneName, int>> zone_indices{
+ vector<std::tuple<ZoneName, domainid_t>> zone_indices{
{ZoneName("powerdns.org."), 1},
{ZoneName("example.org."), 2},
};
cache.add(ZoneName("example.org."), 3);
cache.replace(zone_indices);
- int zoneId = 0;
- bool found = cache.getEntry(ZoneName("example.org."), zoneId);
+ domainid_t zoneId = 0;
+ ZoneName zone("example.org");
+ bool found = cache.getEntry(zone, zoneId);
if (!found || zoneId == 0) {
BOOST_FAIL("zone added while replace was pending not found");
}
}
}
+BOOST_AUTO_TEST_CASE(test_netmask)
+{
+ AuthZoneCache cache;
+ cache.setRefreshInterval(3600);
+
+ // Declare a few zones
+ ZoneName bl("bug.less"); // NOLINT(readability-identifier-length)
+ ZoneName bli("bug.less..inner");
+ ZoneName blo("bug.less..outer");
+ ZoneName fb("fewer.bugs"); // NOLINT(readability-identifier-length)
+ ZoneName bp("bad.puns"); // NOLINT(readability-identifier-length)
+ ZoneName nonexistent("non.existent");
+ cache.add(bli, 42);
+ cache.add(blo, 43);
+ cache.add(fb, 100);
+ cache.add(bp, 1000);
+
+ // Declare a few networks
+ std::string inner{"inner"};
+ std::string outer{"outer"};
+ std::string disjoint{"disjoint"};
+ Netmask innerMask("20.25.4.0/24");
+ Netmask outerMask("20.25.0.0/16");
+ cache.updateNetwork(outerMask, outer);
+ cache.updateNetwork(innerMask, inner);
+
+ // Declare a few views
+ cache.addToView(inner, bli);
+ cache.addToView(outer, blo);
+
+ domainid_t zoneId{0};
+ std::string variant;
+ std::string view;
+ ZoneName search{};
+
+ // Query from no known address
+ bool found = cache.getEntry(bl, zoneId);
+ if (found) {
+ BOOST_FAIL("bug.less lookup should have failed");
+ }
+
+ // Query from inner zone
+ Netmask nm(makeComboAddress("20.25.4.24")); // NOLINT(readability-identifier-length)
+ view = cache.getViewFromNetwork(&nm);
+ BOOST_CHECK_EQUAL(view, inner);
+ variant = cache.getVariantFromView(bl, view);
+ if (nm != innerMask) {
+ BOOST_FAIL("bug.less lookup from inner zone reported wrong network " + nm.toString());
+ }
+ found = cache.getEntry(ZoneName(bl.operator const DNSName&(), variant), zoneId);
+ if (!found) {
+ BOOST_FAIL("bug.less lookup from inner zone should have succeeded");
+ }
+ if (zoneId != 42) {
+ BOOST_FAIL("bug.less lookup from inner zone reported wrong id " + std::to_string(zoneId));
+ }
+ variant = cache.getVariantFromView(fb, view);
+ BOOST_CHECK(variant.empty());
+ if (nm != innerMask) {
+ BOOST_FAIL("fewer.bugs lookup from inner zone reported wrong network " + nm.toString());
+ }
+ found = cache.getEntry(fb, zoneId);
+ if (!found) {
+ BOOST_FAIL("fewer.bugs lookup from inner zone should have succeeded");
+ }
+ if (zoneId != 100) {
+ BOOST_FAIL("fewer.bugs lookup from inner zone reported wrong id " + std::to_string(zoneId));
+ }
+
+ // Query from outer zone
+ nm = makeComboAddress("20.25.20.25");
+ view = cache.getViewFromNetwork(&nm);
+ BOOST_CHECK_EQUAL(view, outer);
+ variant = cache.getVariantFromView(bl, view);
+ if (nm != outerMask) {
+ BOOST_FAIL("bug.less lookup from outer zone reported wrong network " + nm.toString());
+ }
+ found = cache.getEntry(ZoneName(bl.operator const DNSName&(), variant), zoneId);
+ if (!found) {
+ BOOST_FAIL("bug.less lookup from outer zone should have succeeded");
+ }
+ if (zoneId != 43) {
+ BOOST_FAIL("bug.less lookup from outer zone reported wrong id " + std::to_string(zoneId));
+ }
+ variant = cache.getVariantFromView(bp, view);
+ BOOST_CHECK(variant.empty());
+ if (nm != outerMask) {
+ BOOST_FAIL("bad.puns lookup from outer zone reported wrong network " + nm.toString());
+ }
+ found = cache.getEntry(bp, zoneId);
+ if (!found) {
+ BOOST_FAIL("bad.puns lookup from outer zone should have succeeded");
+ }
+ if (zoneId != 1000) {
+ BOOST_FAIL("bad.puns lookup from outer zone reported wrong id " + std::to_string(zoneId));
+ }
+
+ // Query from no particular zone, should clear netmask
+ nm = makeComboAddress("1.2.3.4");
+ view = cache.getViewFromNetwork(&nm);
+ BOOST_CHECK_EQUAL(view, "");
+ variant = cache.getVariantFromView(nonexistent, view);
+ BOOST_CHECK(variant.empty());
+ found = cache.getEntry(nonexistent, zoneId);
+ if (found) {
+ BOOST_FAIL("non.existent lookup from the internet should have failed");
+ }
+ view = cache.getViewFromNetwork(&nm);
+ BOOST_CHECK_EQUAL(view, "");
+ variant = cache.getVariantFromView(bl, view);
+ BOOST_CHECK(variant.empty());
+ found = cache.getEntry(bl, zoneId);
+ if (found) {
+ BOOST_FAIL("bug.less lookup from the internet should have failed");
+ }
+ view = cache.getViewFromNetwork(&nm);
+ BOOST_CHECK_EQUAL(view, "");
+ variant = cache.getVariantFromView(bp, view);
+ BOOST_CHECK(variant.empty());
+ found = cache.getEntry(bp, zoneId);
+ if (!found) {
+ BOOST_FAIL("bad.puns lookup from the internet should have succeeded");
+ }
+ if (zoneId != 1000) {
+ BOOST_FAIL("bad.puns lookup from the internet reported wrong id " + std::to_string(zoneId));
+ }
+}
+
BOOST_AUTO_TEST_SUITE_END();
}
}
g_zoneCache.replace(zone_indices);
+
+ NetmaskTree<string> nettree;
+ for (auto& backend : backends) {
+ vector<pair<Netmask, string>> nettag;
+ backend->networkList(nettag);
+ for (auto& [net, tag] : nettag) {
+ nettree.insert_or_assign(net, tag);
+ }
+ }
+ g_zoneCache.replace(nettree); // FIXME: this needs some smart pending stuff too
+
+ AuthZoneCache::ViewsMap viewsmap;
+ for (auto& backend : backends) {
+ vector<string> views;
+ backend->viewList(views);
+ for (auto& view : views) {
+ vector<ZoneName> zones;
+ backend->viewListZones(view, zones);
+ for (ZoneName& zone : zones) {
+ auto zonename = DNSName(zone);
+ auto variant = zone.getVariant();
+ viewsmap[view][zonename] = variant;
+ }
+ }
+ }
+ g_zoneCache.replace(viewsmap);
}
void UeberBackend::rediscover(string* status)
return false;
}
-bool UeberBackend::getAuth(const ZoneName& target, const QType& qtype, SOAData* soaData, bool cachedOk)
+bool UeberBackend::getAuth(const ZoneName& target, const QType& qtype, SOAData* soaData, bool cachedOk, DNSPacket* pkt_p)
{
// A backend can respond to our authority request with the 'best' match it
// has. For example, when asked for a.b.c.example.com. it might respond with
ZoneName shorter(target);
vector<pair<size_t, SOAData>> bestMatches(backends.size(), pair(target.operator const DNSName&().wirelength() + 1, SOAData()));
+ Netmask remote;
+ if (pkt_p != nullptr) {
+ remote = pkt_p->getRealRemote();
+ }
+ std::string view{};
+ if (g_zoneCache.isEnabled()) {
+ Netmask _remote(remote);
+ view = g_zoneCache.getViewFromNetwork(&_remote);
+ // Remember the view netmask, if applicable, for ECS responses.
+ if (!view.empty() && pkt_p != nullptr) {
+ pkt_p->d_span = _remote;
+ }
+ }
+
bool first = true;
while (first || shorter.chopOff()) {
first = false;
int zoneId{-1};
if (cachedOk && g_zoneCache.isEnabled()) {
- if (g_zoneCache.getEntry(shorter, zoneId)) {
- if (fillSOAFromZoneRecord(shorter, zoneId, soaData)) {
+ std::string variant = g_zoneCache.getVariantFromView(shorter, view);
+ ZoneName _shorter(shorter.operator const DNSName&(), variant);
+ if (g_zoneCache.getEntry(_shorter, zoneId)) {
+ if (fillSOAFromZoneRecord(_shorter, zoneId, soaData)) {
+ // Need to invoke foundTarget() with the same variant part in the
+ // first two arguments, since they are compared as ZoneName, hence
+ // the use of `shorter' rather than `_shorter' here.
if (foundTarget(target, shorter, qtype, soaData, found)) {
return true;
}
void lookupEnd();
/** Determines if we are authoritative for a zone, and at what level */
- bool getAuth(const ZoneName& target, const QType& qtype, SOAData* soaData, bool cachedOk = true);
+ bool getAuth(const ZoneName& target, const QType& qtype, SOAData* soaData, bool cachedOk = true, DNSPacket* pkt_p = nullptr);
/** Load SOA info from backends, ignoring the cache.*/
bool getSOAUncached(const ZoneName& domain, SOAData& soaData);
void getAllDomains(vector<DomainInfo>* domains, bool getSerial, bool include_disabled);
if (!domainInfo.backend->viewAddZone(view, zonename)) {
throw ApiException("Failed to add " + zonename.toString() + " to view " + view);
}
+ // Notify zone cache of the new association
+ if (g_zoneCache.isEnabled()) {
+ g_zoneCache.addToView(view, zonename);
+ }
resp->body = "";
resp->status = 204;
if (!zoneData.domainInfo.backend->viewDelZone(view, zoneData.zoneName)) {
throw ApiException("Failed to remove " + zoneData.zoneName.toString() + " from view " + view);
}
+ // Notify zone cache of the removed association
+ if (g_zoneCache.isEnabled()) {
+ g_zoneCache.removeFromView(view, zoneData.zoneName);
+ }
resp->body = "";
resp->status = 204;
if (!backend.networkSet(network, view)) {
throw ApiException("Failed to setup view " + view + " for network " + network.toString());
}
+ // Notify zone cache of the new association
+ if (g_zoneCache.isEnabled()) {
+ g_zoneCache.updateNetwork(network, view);
+ }
resp->body = "";
resp->status = 204;