]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Add views-related commands to pdnsutil.
authorPeter van Dijk <peter.van.dijk@powerdns.com>
Mon, 7 Apr 2025 13:25:15 +0000 (15:25 +0200)
committerMiod Vallat <miod.vallat@powerdns.com>
Mon, 26 May 2025 11:49:12 +0000 (13:49 +0200)
pdns/pdnsutil.cc

index e7c59df3f39fe4d4b83d6df857ecbe42416c2bfe..12463a79a6c91ebcc554f8c501fdc05bea3a1c4f 100644 (file)
@@ -1,6 +1,7 @@
 #include "dnsname.hh"
 #include "dnsparser.hh"
 #include "dnsrecords.hh"
+#include "iputils.hh"
 #include "qtype.hh"
 #include <boost/smart_ptr/make_shared_array.hpp>
 #ifdef HAVE_CONFIG_H
@@ -122,6 +123,7 @@ static void loadMainConfig(const std::string& configdir)
   ::arg().set("max-generate-steps", "Maximum number of $GENERATE steps when loading a zone from a file")="0";
   ::arg().set("max-include-depth", "Maximum nested $INCLUDE depth when loading a zone from a file")="20";
   ::arg().setSwitch("upgrade-unknown-types","Transparently upgrade known TYPExxx records. Recommended to keep off, except for PowerDNS upgrades until data sources are cleaned up")="no";
+  ::arg().setSwitch("views", "Enable views (variants) of zones, for backends which support them") = "no";
   ::arg().laxFile(configname);
 
   if(!::arg()["load-modules"].empty()) {
@@ -1554,6 +1556,11 @@ static int loadZone(const ZoneName& zone, const string& fname) {
       cerr << "Zone '" << zone << "' was not created." << endl;
       return EXIT_FAILURE;
     }
+    if (zone.hasVariant() && (B.getCapabilities() & DNSBackend::CAP_VIEWS) == 0) {
+      cerr << "None of the configured backends support views." << endl;
+      cerr << "Zone '" << zone << "' was not created." << endl;
+      return EXIT_FAILURE;
+    }
     cerr<<"Creating '"<<zone<<"'"<<endl;
     B.createDomain(zone, DomainInfo::Native, vector<ComboAddress>(), "");
 
@@ -1612,6 +1619,11 @@ static int createZone(const ZoneName &zone, const DNSName& nsname) {
     cerr << "Zone '" << zone << "' was not created." << endl;
     return EXIT_FAILURE;
   }
+  if (zone.hasVariant() && (B.getCapabilities() & DNSBackend::CAP_VIEWS) == 0) {
+    cerr << "None of the configured backends support views." << endl;
+    cerr << "Zone '" << zone << "' was not created." << endl;
+    return EXIT_FAILURE;
+  }
 
   DNSResourceRecord rr;
   rr.qname = zone.operator const DNSName&();
@@ -1641,7 +1653,7 @@ static int createZone(const ZoneName &zone, const DNSName& nsname) {
   cerr<<"Creating empty zone '"<<zone<<"'"<<endl;
   B.createDomain(zone, DomainInfo::Native, vector<ComboAddress>(), "");
   if(!B.getDomainInfo(zone, di)) {
-    cerr << "Zone '" << zone << "' was not created!" << endl;
+    cerr << "Zone '" << zone << "' was not created." << endl;
     return EXIT_FAILURE;
   }
 
@@ -1886,6 +1898,10 @@ static int listAllZones(const std::string_view synopsis, const string &type="")
   vector<DomainInfo> domains;
   B.getAllDomains(&domains, false, g_verbose);
 
+  // Sort results, so that domains which have variants will appear
+  // grouped in the output.
+  std::sort(domains.begin(), domains.end());
+
   int count = 0;
   for (const auto& di: domains) {
     if (di.kind == kindFilter || kindFilter == -1) {
@@ -2176,6 +2192,10 @@ static bool showZone(DNSSECKeeper& dnsseckeeper, const ZoneName& zone, bool expo
   }
   if (!exportDS) {
     cout<<"This is a "<<DomainInfo::getKindString(di.kind)<<" zone"<<endl;
+    auto variant = di.zone.getVariant();
+    if (!variant.empty()) {
+      cout<<"Variant: " << variant << endl;
+    }
     if (di.isPrimaryType()) {
       cout<<"Last SOA serial number we notified: "<<di.notified_serial<<" ";
       SOAData sd;
@@ -3192,6 +3212,11 @@ static int createSecondaryZone(vector<string>& cmds, const std::string_view syno
     cerr << "Zone '" << zone << "' was not created." << endl;
     return EXIT_FAILURE;
   }
+  if (zone.hasVariant() && (B.getCapabilities() & DNSBackend::CAP_VIEWS) == 0) {
+    cerr << "None of the configured backends support views." << endl;
+    cerr << "Zone '" << zone << "' was not created." << endl;
+    return EXIT_FAILURE;
+  }
   vector<ComboAddress> primaries;
   for (unsigned i=2; i < cmds.size(); i++) {
     primaries.emplace_back(cmds.at(i), 53);
@@ -3199,7 +3224,7 @@ static int createSecondaryZone(vector<string>& cmds, const std::string_view syno
   cerr << "Creating secondary zone '" << zone << "', with primaries '" << comboAddressVecToString(primaries) << "'" << endl;
   B.createDomain(zone, DomainInfo::Secondary, primaries, "");
   if(!B.getDomainInfo(zone, di)) {
-    cerr << "Zone '" << zone << "' was not created!" << endl;
+    cerr << "Zone '" << zone << "' was not created." << endl;
     return EXIT_FAILURE;
   }
   return EXIT_SUCCESS;
@@ -4359,8 +4384,12 @@ static int B2BMigrate(vector<string>& cmds, const std::string_view synopsis)
     DNSResourceRecord rr; // NOLINT(readability-identifier-length)
     cout<<"Processing '"<<di.zone<<"'"<<endl;
     // create zone
+    if (di.zone.hasVariant() && (tgt->getCapabilities() & DNSBackend::CAP_VIEWS) == 0) {
+      cerr << "Target backend does not support views." << endl;
+      throw PDNSException("Failed to create zone");
+    }
     if (!tgt->createDomain(di.zone, di.kind, di.primaries, di.account)) {
-       throw PDNSException("Failed to create zone");
+      throw PDNSException("Failed to create zone");
     }
     if (!tgt->getDomainInfo(di.zone, di_new)) {
       throw PDNSException("Failed to create zone");
@@ -4496,7 +4525,23 @@ static int backendLookup(vector<string>& cmds, const std::string_view synopsis)
     type = DNSRecordContent::TypeToNumber(cmds.at(3));
   }
 
-  DNSName name{cmds.at(2)};
+  ZoneName name{cmds.at(2)};
+  domainid_t domain_id{UnknownDomainID};
+
+  if (name.hasVariant()) {
+    ZoneName zone(name);
+    do {
+      SOAData soa;
+      if (matchingBackend->getSOA(zone, UnknownDomainID, soa)) {
+        domain_id = soa.domain_id;
+        break;
+      }
+    } while (zone.chopOff());
+    if (domain_id == UnknownDomainID) {
+      cerr << "Backend found no matching zone" << endl;
+      return 1;
+    }
+  }
 
   DNSPacket queryPacket(true);
   Netmask clientNetmask;
@@ -4505,7 +4550,7 @@ static int backendLookup(vector<string>& cmds, const std::string_view synopsis)
     queryPacket.setRealRemote(clientNetmask);
   }
 
-  matchingBackend->lookup(type, name, -1, &queryPacket);
+  matchingBackend->lookup(type, name.operator const DNSName&(), domain_id, &queryPacket);
 
   bool found = false;
   DNSZoneRecord resultZoneRecord;
@@ -4530,6 +4575,112 @@ static int backendLookup(vector<string>& cmds, const std::string_view synopsis)
   return 0;
 }
 
+static int listView(vector<string>& cmds, const std::string_view synopsis)
+{
+  if (cmds.size() != 2) {
+    return usage(synopsis);
+  }
+
+  UtilBackend B("default"); //NOLINT(readability-identifier-length)
+
+  vector<ZoneName> ret;
+  B.viewListZones(cmds.at(1), ret);
+
+  for (const auto& zone : ret) {
+    cout << zone << endl;
+  }
+  return 0;
+}
+
+static int listViews(vector<string>& cmds, const std::string_view synopsis)
+{
+  if (cmds.size() != 1) {
+    return usage(synopsis);
+  }
+
+  UtilBackend B("default"); //NOLINT(readability-identifier-length)
+
+  vector<string> ret;
+  B.viewList(ret);
+
+  for (const auto& view : ret) {
+    cout << view << endl;
+  }
+  return 0;
+}
+
+static int viewAddZone(vector<string>& cmds, const std::string_view synopsis)
+{
+  if (cmds.size() < 3) {
+    return usage(synopsis);
+  }
+
+  UtilBackend B("default"); //NOLINT(readability-identifier-length)
+
+  string view{cmds.at(1)};
+  ZoneName zone{cmds.at(2)};
+  if (!B.viewAddZone(view, zone)) {
+    cerr<<"Operation failed."<<endl;
+    return 1;
+ }
+  return 0;
+}
+
+static int viewDelZone(vector<string>& cmds, const std::string_view synopsis)
+{
+  if (cmds.size() < 3) {
+    return usage(synopsis);
+  }
+
+  UtilBackend B("default"); //NOLINT(readability-identifier-length)
+
+  string view{cmds.at(1)};
+  ZoneName zone{cmds.at(2)};
+  if (!B.viewDelZone(view, zone)) {
+    cerr<<"Operation failed."<<endl;
+    return 1;
+ }
+  return 0;
+}
+
+static int listNetwork(vector<string>& cmds, const std::string_view synopsis)
+{
+  if (cmds.empty()) {
+    return usage(synopsis);
+  }
+
+  UtilBackend B("default"); //NOLINT(readability-identifier-length)
+
+  vector<pair<Netmask, string> > ret;
+
+  B.networkList(ret);
+
+  for (auto &[net, view] : ret) {
+    cout<<net.toString()<<"\t"<<view<<endl; // FIXME: this prints "invalid" when there is no match
+  }
+  return 0;
+}
+
+static int setNetwork(vector<string>& cmds, const std::string_view synopsis)
+{
+  if (cmds.size() < 2) {
+    return usage(synopsis);
+  }
+
+  UtilBackend B("default"); //NOLINT(readability-identifier-length)
+
+  Netmask net{cmds.at(1)};
+  string view{};
+  if (cmds.size() > 2) {
+    view = cmds.at(2);
+  }
+  if (!B.networkSet(net, view)) {
+    cerr<<"Operation failed."<<endl;
+    return 1;
+ }
+  return 0;
+}
+
 enum commandGroup {
   GROUP_AUTOPRIMARY,
   GROUP_CATALOG,
@@ -4541,6 +4692,7 @@ enum commandGroup {
   GROUP_NSEC3,
   GROUP_TSIGKEY,
   GROUP_ZONEKEY,
+  GROUP_VIEWS,
   GROUP_OTHER,
   GROUP_LAST,
   GROUP_FIRST = GROUP_AUTOPRIMARY,
@@ -4557,6 +4709,7 @@ static const std::array<std::string_view, GROUP_LAST> groupNames{
   "NSEC3",
   "TSIG key",
   "Zone key",
+  "Views",
   "Other"
 };
 
@@ -4729,9 +4882,18 @@ static const std::unordered_map<std::string, commandDispatcher> commands{
   {"list-member-zones", {true, listMemberZones, GROUP_ZONE,
    "list-member-zones CATALOG",
    "\tList all members of catalog zone CATALOG"}},
+  {"list-networks", {true, listNetwork, GROUP_VIEWS,
+   "list-networks",
+   "\tList all defined networks with their chosen views"}},
   {"list-tsig-keys", {true, listTSIGKeys, GROUP_TSIGKEY,
    "list-tsig-keys",
    "\tList all TSIG keys"}},
+  {"list-view", {true, listView, GROUP_VIEWS,
+   "list-view VIEW",
+   "\tList all zones within VIEW"}},
+  {"list-views", {true, listViews, GROUP_VIEWS,
+   "list-view",
+   "\tList all view names"}},
   {"list-zone", {true, listZone, GROUP_ZONE,
    "list-zone ZONE",
    "\tList zone contents"}},
@@ -4786,6 +4948,9 @@ static const std::unordered_map<std::string, commandDispatcher> commands{
    "set-meta ZONE KIND [VALUE...]",
    "\tSet zone metadata, replacing all existing records of KIND, optionally\n"
    "\tproviding a value. An omitted value clears the metadata"}},
+  {"set-network", {true, setNetwork, GROUP_VIEWS,
+   "set-network NET [VIEW]",
+   "\tSet the view for a network, or delete if no view argument."}},
   {"set-nsec3", {true, setNsec3, GROUP_NSEC3,
    "set-nsec3 ZONE ['PARAMS' [narrow]]",
    "\tEnable NSEC3 with PARAMS (default: '1 0 0 -'). Optionally narrow"}},
@@ -4838,6 +5003,12 @@ static const std::unordered_map<std::string, commandDispatcher> commands{
    "\tDisable sending CDS responses for ZONE"}},
   {"verify-crypto", {true, verifyCrypto, GROUP_OTHER,
    "verify-crypto FILENAME", ""}}, // TODO: short help line
+  {"view-add-zone", {true, viewAddZone, GROUP_VIEWS,
+   "view-add-zone VIEW ZONE..VARIANT",
+   "\tAdd a zone variant to a view"}},
+  {"view-del-zone", {true, viewDelZone, GROUP_VIEWS,
+   "view-del-zone VIEW ZONE..VARIANT",
+   "\tRemove a zone variant from a view"}},
   {"zonemd-verify-file", {true, zonemdVerifyFile, GROUP_ZONE,
    "zonemd-verify-file ZONE FILENAME",
    "\tValidate ZONEMD for ZONE"}}