]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Add a better, modern interface for adding DynBlocks from Lua
authorRemi Gacogne <remi.gacogne@powerdns.com>
Fri, 10 Nov 2023 14:51:52 +0000 (15:51 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Tue, 14 Nov 2023 14:43:35 +0000 (15:43 +0100)
pdns/dnsdist-console.cc
pdns/dnsdist-dynblocks.hh
pdns/dnsdist-lua.cc
pdns/dnsdistdist/dnsdist-dynblocks.cc
pdns/dnsdistdist/docs/reference/config.rst

index 9dfe8159fe504c2df196bb48446f65c8a350eaf0..18af1c4a51e47fa6d054d4add2143d0135568b47 100644 (file)
@@ -471,6 +471,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "addDNSCryptBind", true, "\"127.0.0.1:8443\", \"provider name\", \"/path/to/resolver.cert\", \"/path/to/resolver.key\", {reusePort=false, tcpFastOpenQueueSize=0, interface=\"\", cpus={}}", "listen to incoming DNSCrypt queries on 127.0.0.1 port 8443, with a provider name of `provider name`, using a resolver certificate and associated key stored respectively in the `resolver.cert` and `resolver.key` files. The fifth optional parameter is a table of parameters" },
   { "addDOHLocal", true, "addr, certFile, keyFile [, urls [, vars]]", "listen to incoming DNS over HTTPS queries on the specified address using the specified certificate and key. The last two parameters are tables" },
   { "addDOQLocal", true, "addr, certFile, keyFile [, vars]", "listen to incoming DNS over QUIC queries on the specified address using the specified certificate and key. The last parameter is a table" },
+  { "addDynamicBlock", true, "address, message[, action [, seconds [clientIPMask [, clientIPPortMask]]]]", "block the set of addresses with message `msg`, for `seconds` seconds (10 by default), applying `action` (default to the one set with `setDynBlocksAction()`)" },
   { "addDynBlocks", true, "addresses, message[, seconds[, action]]", "block the set of addresses with message `msg`, for `seconds` seconds (10 by default), applying `action` (default to the one set with `setDynBlocksAction()`)" },
   { "addDynBlockSMT", true, "names, message[, seconds [, action]]", "block the set of names with message `msg`, for `seconds` seconds (10 by default), applying `action` (default to the one set with `setDynBlocksAction()`)" },
   { "addLocal", true, "addr [, {doTCP=true, reusePort=false, tcpFastOpenQueueSize=0, interface=\"\", cpus={}}]", "add `addr` to the list of addresses we listen on" },
index 9228ce202492c1517c0ebebbca59b24bcfec0738..25c1036c231d3e524c2c20b7b7d8c4349a501827 100644 (file)
@@ -449,4 +449,8 @@ private:
   static size_t s_topN;
 };
 
+namespace dnsdist::DynamicBlocks {
+bool addOrRefreshBlock(NetmaskTree<DynBlock, AddressAndPortRange>& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const std::string& reason, unsigned int duration, DNSAction::Action action, bool warning, bool beQuiet);
+bool addOrRefreshBlockSMT(SuffixMatchTree<DynBlock>& blocks, const struct timespec& now, const DNSName& name, const std::string& reason, unsigned int duration, DNSAction::Action action, bool beQuiet);
+}
 #endif /* DISABLE_DYNBLOCKS */
index 2b47f26007f7a4a65028d14e470830e62d5fa259..c9fdeb72e54b9549ec55b07d5671f3fabe4c8233 100644 (file)
@@ -1468,43 +1468,6 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
                          g_dynblockNMG.setState(slow);
                        });
 
-  luaCtx.writeFunction("addDynBlockSMT",
-                       [](const LuaArray<std::string>& names, const std::string& msg, boost::optional<int> seconds, boost::optional<DNSAction::Action> action) {
-                         if (names.empty()) {
-                           return;
-                         }
-                         setLuaSideEffect();
-                         auto slow = g_dynblockSMT.getCopy();
-                         struct timespec until, now;
-                         gettime(&now);
-                         until = now;
-                         int actualSeconds = seconds ? *seconds : 10;
-                         until.tv_sec += actualSeconds;
-
-                         for (const auto& capair : names) {
-                           unsigned int count = 0;
-                           DNSName domain(capair.second);
-                           domain.makeUsLowerCase();
-                           auto got = slow.lookup(domain);
-                           bool expired = false;
-                           if (got) {
-                             if (until < got->until) // had a longer policy
-                               continue;
-                             if (now < got->until) // only inherit count on fresh query we are extending
-                               count = got->blocks;
-                             else
-                               expired = true;
-                           }
-
-                           DynBlock db{msg, until, domain, (action ? *action : DNSAction::Action::None)};
-                           db.blocks = count;
-                           if (!got || expired)
-                             warnlog("Inserting dynamic block for %s for %d seconds: %s", domain, actualSeconds, msg);
-                           slow.add(domain, std::move(db));
-                         }
-                         g_dynblockSMT.setState(slow);
-                       });
-
   luaCtx.writeFunction("setDynBlocksAction", [](DNSAction::Action action) {
     if (!checkConfigurationTime("setDynBlocksAction")) {
       return;
@@ -1519,6 +1482,65 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
   });
 #endif /* DISABLE_DEPRECATED_DYNBLOCK */
 
+  luaCtx.writeFunction("addDynBlockSMT",
+                       [](const LuaArray<std::string>& names, const std::string& msg, boost::optional<int> seconds, boost::optional<DNSAction::Action> action) {
+    if (names.empty()) {
+      return;
+    }
+    setLuaSideEffect();
+    struct timespec now;
+    gettime(&now);
+    unsigned int actualSeconds = seconds ? *seconds : 10;
+
+    bool needUpdate = false;
+    auto slow = g_dynblockSMT.getCopy();
+    for (const auto& capair : names) {
+      DNSName domain(capair.second);
+      domain.makeUsLowerCase();
+
+      if (dnsdist::DynamicBlocks::addOrRefreshBlockSMT(slow, now, domain, msg, actualSeconds, action ? *action : DNSAction::Action::None, false)) {
+        needUpdate = true;
+      }
+    }
+
+    if (needUpdate) {
+      g_dynblockSMT.setState(slow);
+    }
+  });
+
+  luaCtx.writeFunction("addDynamicBlock",
+                       [](const boost::variant<ComboAddress, std::string>& clientIP, const std::string& msg, const boost::optional<DNSAction::Action> action, const boost::optional<int> seconds, boost::optional<uint8_t> clientIPMask, boost::optional<uint8_t> clientIPPortMask) {
+    setLuaSideEffect();
+
+    ComboAddress clientIPCA;
+    if (clientIP.type() == typeid(ComboAddress)) {
+      clientIPCA = boost::get<ComboAddress>(clientIP);
+    }
+    else {
+      auto clientIPStr = boost::get<std::string>(clientIP);
+      try {
+        clientIPCA = ComboAddress(clientIPStr);
+      }
+      catch (const std::exception& exp) {
+        errlog("addDynamicBlock: Unable to parse '%s': %s", clientIPStr, exp.what());
+        return;
+      }
+      catch (const PDNSException& exp) {
+        errlog("addDynamicBlock: Unable to parse '%s': %s", clientIPStr, exp.reason);
+        return;
+      }
+    }
+    AddressAndPortRange target(clientIPCA, clientIPMask ? *clientIPMask : (clientIPCA.isIPv4() ? 32 : 128), clientIPPortMask ? *clientIPPortMask : 0);
+    unsigned int actualSeconds = seconds ? *seconds : 10;
+
+    struct timespec now;
+    gettime(&now);
+    auto slow = g_dynblockNMG.getCopy();
+    if (dnsdist::DynamicBlocks::addOrRefreshBlock(slow, now, target, msg, actualSeconds, action ? *action : DNSAction::Action::None, false, false)) {
+      g_dynblockNMG.setState(slow);
+    }
+  });
+
   luaCtx.writeFunction("setDynBlocksPurgeInterval", [](uint64_t interval) {
     DynBlockMaintenance::s_expiredDynBlocksPurgeInterval = interval;
   });
index 928aff7f9b4a2eefb59a5f7f78165f9c2f786d57..fbb2fefa2f1d3df7c780a8f260cd036cb87589d7 100644 (file)
@@ -193,32 +193,27 @@ static DNSAction::Action getActualAction(const DynBlock& block)
   return g_dynBlockAction;
 }
 
-void DynBlockRulesGroup::addOrRefreshBlock(boost::optional<NetmaskTree<DynBlock, AddressAndPortRange> >& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const DynBlockRule& rule, bool& updated, bool warning)
+namespace dnsdist::DynamicBlocks {
+  bool addOrRefreshBlock(NetmaskTree<DynBlock, AddressAndPortRange>& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const std::string& reason, unsigned int duration, DNSAction::Action action, bool warning, bool beQuiet)
 {
-  /* network exclusions are address-based only (no port) */
-  if (d_excludedSubnets.match(requestor.getNetwork())) {
-    /* do not add a block for excluded subnets */
-    return;
-  }
-
-  if (!blocks) {
-    blocks = g_dynblockNMG.getCopy();
-  }
-  struct timespec until = now;
-  until.tv_sec += rule.d_blockDuration;
   unsigned int count = 0;
-  const auto& got = blocks->lookup(requestor);
   bool expired = false;
   bool wasWarning = false;
   bool bpf = false;
+  struct timespec until = now;
+  until.tv_sec += duration;
 
+  DynBlock db{reason, until, DNSName(), warning ? DNSAction::Action::NoOp : action};
+  db.warning = warning;
+
+  const auto& got = blocks.lookup(requestor);
   if (got) {
     bpf = got->second.bpf;
 
     if (warning && !got->second.warning) {
       /* we have an existing entry which is not a warning,
          don't override it */
-      return;
+      return false;
     }
     else if (!warning && got->second.warning) {
       wasWarning = true;
@@ -226,7 +221,7 @@ void DynBlockRulesGroup::addOrRefreshBlock(boost::optional<NetmaskTree<DynBlock,
     else {
       if (until < got->second.until) {
         // had a longer policy
-        return;
+        return false;
       }
     }
 
@@ -239,9 +234,8 @@ void DynBlockRulesGroup::addOrRefreshBlock(boost::optional<NetmaskTree<DynBlock,
     }
   }
 
-  DynBlock db{rule.d_blockReason, until, DNSName(), warning ? DNSAction::Action::NoOp : rule.d_action};
   db.blocks = count;
-  db.warning = warning;
+
   if (!got || expired || wasWarning) {
     const auto actualAction = getActualAction(db);
     if (g_defaultBPFFilter &&
@@ -260,28 +254,24 @@ void DynBlockRulesGroup::addOrRefreshBlock(boost::optional<NetmaskTree<DynBlock,
       }
     }
 
-    if (!d_beQuiet) {
-      warnlog("Inserting %s%sdynamic block for %s for %d seconds: %s", warning ? "(warning) " :"", bpf ? "eBPF " : "", requestor.toString(), rule.d_blockDuration, rule.d_blockReason);
+    if (!beQuiet) {
+      warnlog("Inserting %s%sdynamic block for %s for %d seconds: %s", warning ? "(warning) " :"", bpf ? "eBPF " : "", requestor.toString(), duration, reason);
     }
   }
 
   db.bpf = bpf;
 
-  blocks->insert(requestor).second = std::move(db);
+  blocks.insert(requestor).second = std::move(db);
 
-  updated = true;
+  return true;
 }
 
-void DynBlockRulesGroup::addOrRefreshBlockSMT(SuffixMatchTree<DynBlock>& blocks, const struct timespec& now, const DNSName& name, const DynBlockRule& rule, bool& updated)
+bool addOrRefreshBlockSMT(SuffixMatchTree<DynBlock>& blocks, const struct timespec& now, const DNSName& name, const std::string& reason, unsigned int duration, DNSAction::Action action, bool beQuiet)
 {
-  if (d_excludedDomains.check(name)) {
-    /* do not add a block for excluded domains */
-    return;
-  }
-
   struct timespec until = now;
-  until.tv_sec += rule.d_blockDuration;
+  until.tv_sec += duration;
   unsigned int count = 0;
+  DynBlock db{reason, until, name.makeLowerCase(), action};
   /* be careful, if you try to insert a longer suffix
      lookup() might return a shorter one if it is
      already in the tree as a final node */
@@ -294,7 +284,7 @@ void DynBlockRulesGroup::addOrRefreshBlockSMT(SuffixMatchTree<DynBlock>& blocks,
   if (got) {
     if (until < got->until) {
       // had a longer policy
-      return;
+      return false;
     }
 
     if (now < got->until) {
@@ -306,14 +296,39 @@ void DynBlockRulesGroup::addOrRefreshBlockSMT(SuffixMatchTree<DynBlock>& blocks,
     }
   }
 
-  DynBlock db{rule.d_blockReason, until, name.makeLowerCase(), rule.d_action};
   db.blocks = count;
 
-  if (!d_beQuiet && (!got || expired)) {
-    warnlog("Inserting dynamic block for %s for %d seconds: %s", name, rule.d_blockDuration, rule.d_blockReason);
+  if (!beQuiet && (!got || expired)) {
+    warnlog("Inserting dynamic block for %s for %d seconds: %s", name, duration, reason);
   }
   blocks.add(name, std::move(db));
-  updated = true;
+  return true;
+}
+}
+
+void DynBlockRulesGroup::addOrRefreshBlock(boost::optional<NetmaskTree<DynBlock, AddressAndPortRange> >& blocks, const struct timespec& now, const AddressAndPortRange& requestor, const DynBlockRule& rule, bool& updated, bool warning)
+{
+  /* network exclusions are address-based only (no port) */
+  if (d_excludedSubnets.match(requestor.getNetwork())) {
+    /* do not add a block for excluded subnets */
+    return;
+  }
+
+  if (!blocks) {
+    blocks = g_dynblockNMG.getCopy();
+  }
+
+  updated = dnsdist::DynamicBlocks::addOrRefreshBlock(*blocks, now, requestor, rule.d_blockReason, rule.d_blockDuration, rule.d_action, warning, d_beQuiet);
+}
+
+void DynBlockRulesGroup::addOrRefreshBlockSMT(SuffixMatchTree<DynBlock>& blocks, const struct timespec& now, const DNSName& name, const DynBlockRule& rule, bool& updated)
+{
+  if (d_excludedDomains.check(name)) {
+    /* do not add a block for excluded domains */
+    return;
+  }
+
+  updated = dnsdist::DynamicBlocks::addOrRefreshBlockSMT(blocks, now, name, rule.d_blockReason, rule.d_blockDuration, rule.d_action, d_beQuiet);
 }
 
 void DynBlockRulesGroup::processQueryRules(counts_t& counts, const struct timespec& now)
@@ -332,7 +347,7 @@ void DynBlockRulesGroup::processQueryRules(counts_t& counts, const struct timesp
 
   for (const auto& shard : g_rings.d_shards) {
     auto rl = shard->queryRing.lock();
-    for(const auto& c : *rl) {
+    for (const auto& c : *rl) {
       if (now < c.when) {
         continue;
       }
index 6d59930d51c2cbcbfe43cdfe45a68dd8113a5c5c..95a65724a41d88f9e9887c7d50c341d1c63e9cac 100644 (file)
@@ -1407,6 +1407,22 @@ Status, Statistics and More
 Dynamic Blocks
 --------------
 
+.. function:: addDynamicBlock(address, message[, action [, seconds [, clientIPMask [, clientIPPortMask]]]])
+
+  .. versionadded:: 1.9.0
+
+  Manually block an IP address or range with ``message`` for (optionally) a number of seconds.
+  The default number of seconds to block for is 10.
+
+  :param address: A :class:`ComboAddress` or string representing an IPv4 or IPv6 address
+  :param string message: The message to show next to the blocks
+  :param int action: The action to take when the dynamic block matches, see :ref:`DNSAction <DNSAction>`. (default to DNSAction.None, meaning the one set with :func:`setDynBlocksAction` is used)
+  :param int seconds: The number of seconds this block to expire
+  :param int clientIPMask: The network mask to apply to the address. Default is 32 for IPv4, 128 for IPv6.
+  :param int clientIPPortMask: The port mask to use to specify a range of ports to match, when the clients are behind a CG-NAT.
+
+  Please see the documentation for :func:`setDynBlocksAction` to confirm which actions are supported by the action paramater.
+
 .. function:: addDynBlocks(addresses, message[, seconds=10[, action]])
 
   Block a set of addresses with ``message`` for (optionally) a number of seconds.