]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Add a new query rules chain triggered after a cache miss
authorRemi Gacogne <remi.gacogne@powerdns.com>
Fri, 8 Mar 2024 15:14:17 +0000 (16:14 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Fri, 29 Mar 2024 14:34:51 +0000 (15:34 +0100)
This new chain of rules allows postponing the decision of what to
do with the query to after a cache-lookup has been done. This is
particularly useful when dealing with abuse: we might want to allow
cache hits to be processed normally since they are cheap while dropping/
refusing/routing to a different pool queries that result in a cache
miss.

pdns/dnsdistdist/dnsdist-lua-actions.cc
pdns/dnsdistdist/dnsdist-lua-rules.cc
pdns/dnsdistdist/dnsdist-rule-chains.cc
pdns/dnsdistdist/dnsdist-rule-chains.hh
pdns/dnsdistdist/dnsdist-web.cc
pdns/dnsdistdist/dnsdist.cc
pdns/dnsdistdist/dnsdist.hh
pdns/dnsdistdist/docs/reference/rules-management.rst
pdns/dnsdistdist/test-dnsdisttcp_cc.cc

index 0112fd17c3426baddf04b0769a8901f5d8dd3dda..51348375f016fec54ffa17801f30f98f0f186d74 100644 (file)
@@ -2481,13 +2481,26 @@ void setupLuaActions(LuaContext& luaCtx)
     return std::make_shared<dnsdist::rules::RuleAction>(ruleaction);
   });
 
-  luaCtx.writeFunction("addAction", [](const luadnsrule_t& var, boost::variant<std::shared_ptr<DNSAction>, std::shared_ptr<DNSResponseAction>> era, boost::optional<luaruleparams_t> params) {
-    if (era.type() != typeid(std::shared_ptr<DNSAction>)) {
-      throw std::runtime_error("addAction() can only be called with query-related actions, not response-related ones. Are you looking for addResponseAction()?");
-    }
+  for (const auto& chain : dnsdist::rules::getRuleChains()) {
+    auto fullName = std::string("add") + chain.prefix + std::string("Action");
+    luaCtx.writeFunction(fullName, [&fullName, &chain](const luadnsrule_t& var, boost::variant<std::shared_ptr<DNSAction>, std::shared_ptr<DNSResponseAction>> era, boost::optional<luaruleparams_t> params) {
+      if (era.type() != typeid(std::shared_ptr<DNSAction>)) {
+        throw std::runtime_error(fullName + "() can only be called with query-related actions, not response-related ones. Are you looking for addResponseAction()?");
+      }
 
-    addAction(&dnsdist::rules::g_ruleactions, var, boost::get<std::shared_ptr<DNSAction>>(era), params);
-  });
+      addAction(&chain.holder, var, boost::get<std::shared_ptr<DNSAction>>(era), params);
+    });
+    fullName = std::string("get") + chain.prefix + std::string("Action");
+    luaCtx.writeFunction(fullName, [&chain](unsigned int num) {
+      setLuaNoSideEffect();
+      boost::optional<std::shared_ptr<DNSAction>> ret;
+      auto ruleactions = chain.holder.getCopy();
+      if (num < ruleactions.size()) {
+        ret = ruleactions[num].d_action;
+      }
+      return ret;
+    });
+  }
 
   for (const auto& chain : dnsdist::rules::getResponseRuleChains()) {
     const auto fullName = std::string("add") + chain.prefix + std::string("ResponseAction");
@@ -2515,16 +2528,6 @@ void setupLuaActions(LuaContext& luaCtx)
     }
   });
 
-  luaCtx.writeFunction("getAction", [](unsigned int num) {
-    setLuaNoSideEffect();
-    boost::optional<std::shared_ptr<DNSAction>> ret;
-    auto ruleactions = dnsdist::rules::g_ruleactions.getCopy();
-    if (num < ruleactions.size()) {
-      ret = ruleactions[num].d_action;
-    }
-    return ret;
-  });
-
   luaCtx.registerFunction("getStats", &DNSAction::getStats);
   luaCtx.registerFunction("reload", &DNSAction::reload);
   luaCtx.registerFunction("reload", &DNSResponseAction::reload);
index 6209a54ac468dece079a2f07bd22f10b5c6e0071..a4d7adbcfdd32714dd093feab654f5f5b39199a9 100644 (file)
@@ -362,57 +362,66 @@ void setupLuaRules(LuaContext& luaCtx)
       auto rules = chain.holder.getLocal();
       return rulesToString(getTopRules(*rules, (top ? *top : 10)), vars);
     });
+
+    luaCtx.writeFunction("clear" + chain.prefix + "ResponseRules", [&chain]() {
+      setLuaSideEffect();
+      chain.holder.modify([](std::remove_reference_t<decltype(chain.holder)>::value_type& ruleactions) {
+        ruleactions.clear();
+      });
+    });
   }
 
-  luaCtx.writeFunction("rmRule", [](const boost::variant<unsigned int, std::string>& identifier) {
-    rmRule(&dnsdist::rules::g_ruleactions, identifier);
-  });
+  for (const auto& chain : dnsdist::rules::getRuleChains()) {
+    luaCtx.writeFunction("show" + chain.prefix + "Rules", [&chain](boost::optional<ruleparams_t> vars) {
+      showRules(&chain.holder, vars);
+    });
+    luaCtx.writeFunction("rm" + chain.prefix + "Rule", [&chain](const boost::variant<unsigned int, std::string>& identifier) {
+      rmRule(&chain.holder, identifier);
+    });
+    luaCtx.writeFunction("mv" + chain.prefix + "RuleToTop", [&chain]() {
+      moveRuleToTop(&chain.holder);
+    });
+    luaCtx.writeFunction("mv" + chain.prefix + "Rule", [&chain](unsigned int from, unsigned int dest) {
+      mvRule(&chain.holder, from, dest);
+    });
+    luaCtx.writeFunction("get" + chain.prefix + "Rule", [&chain](const boost::variant<int, std::string>& selector) -> boost::optional<dnsdist::rules::RuleAction> {
+      auto rules = chain.holder.getLocal();
+      return getRuleFromSelector(*rules, selector);
+    });
 
-  luaCtx.writeFunction("mvRuleToTop", []() {
-    moveRuleToTop(&dnsdist::rules::g_ruleactions);
-  });
+    luaCtx.writeFunction("getTop" + chain.prefix + "Rules", [&chain](boost::optional<unsigned int> top) {
+      setLuaNoSideEffect();
+      auto rules = chain.holder.getLocal();
+      return toLuaArray(getTopRules(*rules, (top ? *top : 10)));
+    });
 
-  luaCtx.writeFunction("mvRule", [](unsigned int from, unsigned int dest) {
-    mvRule(&dnsdist::rules::g_ruleactions, from, dest);
-  });
+    luaCtx.writeFunction("top" + chain.prefix + "Rules", [&chain](boost::optional<unsigned int> top, boost::optional<ruleparams_t> vars) {
+      setLuaNoSideEffect();
+      auto rules = chain.holder.getLocal();
+      return rulesToString(getTopRules(*rules, (top ? *top : 10)), vars);
+    });
 
-  luaCtx.writeFunction("clearRules", []() {
-    setLuaSideEffect();
-    dnsdist::rules::g_ruleactions.modify([](decltype(dnsdist::rules::g_ruleactions)::value_type& ruleactions) {
-      ruleactions.clear();
+    luaCtx.writeFunction("clear" + chain.prefix + "Rules", [&chain]() {
+      setLuaSideEffect();
+      chain.holder.modify([](std::remove_reference_t<decltype(chain.holder)>::value_type& ruleactions) {
+        ruleactions.clear();
+      });
     });
-  });
 
-  luaCtx.writeFunction("setRules", [](const LuaArray<std::shared_ptr<dnsdist::rules::RuleAction>>& newruleactions) {
-    setLuaSideEffect();
-    dnsdist::rules::g_ruleactions.modify([newruleactions](decltype(dnsdist::rules::g_ruleactions)::value_type& gruleactions) {
-      gruleactions.clear();
-      for (const auto& pair : newruleactions) {
-        const auto& newruleaction = pair.second;
-        if (newruleaction->d_action) {
-          auto rule = newruleaction->d_rule;
-          gruleactions.push_back({std::move(rule), newruleaction->d_action, newruleaction->d_name, newruleaction->d_id, newruleaction->d_creationOrder});
+    luaCtx.writeFunction("set" + chain.prefix + "Rules", [&chain](const LuaArray<std::shared_ptr<dnsdist::rules::RuleAction>>& newruleactions) {
+      setLuaSideEffect();
+      chain.holder.modify([newruleactions](std::remove_reference_t<decltype(chain.holder)>::value_type& gruleactions) {
+        gruleactions.clear();
+        for (const auto& pair : newruleactions) {
+          const auto& newruleaction = pair.second;
+          if (newruleaction->d_action) {
+            auto rule = newruleaction->d_rule;
+            gruleactions.push_back({std::move(rule), newruleaction->d_action, newruleaction->d_name, newruleaction->d_id, newruleaction->d_creationOrder});
+          }
         }
-      }
+      });
     });
-  });
-
-  luaCtx.writeFunction("getRule", [](const boost::variant<int, std::string>& selector) -> boost::optional<dnsdist::rules::RuleAction> {
-    auto rules = dnsdist::rules::g_ruleactions.getLocal();
-    return getRuleFromSelector(*rules, selector);
-  });
-
-  luaCtx.writeFunction("getTopRules", [](boost::optional<unsigned int> top) {
-    setLuaNoSideEffect();
-    auto rules = dnsdist::rules::g_ruleactions.getLocal();
-    return toLuaArray(getTopRules(*rules, (top ? *top : 10)));
-  });
-
-  luaCtx.writeFunction("topRules", [](boost::optional<unsigned int> top, boost::optional<ruleparams_t> vars) {
-    setLuaNoSideEffect();
-    auto rules = dnsdist::rules::g_ruleactions.getLocal();
-    return rulesToString(getTopRules(*rules, (top ? *top : 10)), vars);
-  });
+  }
 
   luaCtx.writeFunction("MaxQPSIPRule", [](unsigned int qps, boost::optional<unsigned int> ipv4trunc, boost::optional<unsigned int> ipv6trunc, boost::optional<unsigned int> burst, boost::optional<unsigned int> expiration, boost::optional<unsigned int> cleanupDelay, boost::optional<unsigned int> scanFraction, boost::optional<unsigned int> shards) {
     return std::shared_ptr<DNSRule>(new MaxQPSIPRule(qps, (burst ? *burst : qps), (ipv4trunc ? *ipv4trunc : 32), (ipv6trunc ? *ipv6trunc : 64), (expiration ? *expiration : 300), (cleanupDelay ? *cleanupDelay : 60), (scanFraction ? *scanFraction : 10), (shards ? *shards : 10)));
@@ -627,10 +636,6 @@ void setupLuaRules(LuaContext& luaCtx)
     return std::shared_ptr<DNSRule>(new EDNSOptionRule(optcode));
   });
 
-  luaCtx.writeFunction("showRules", [](boost::optional<ruleparams_t> vars) {
-    showRules(&dnsdist::rules::g_ruleactions, vars);
-  });
-
   luaCtx.writeFunction("RDRule", []() {
     return std::shared_ptr<DNSRule>(new RDRule());
   });
index 62cf8435c5a9f537b7cdf927d1ed12c0fe8f687b..1c79fd0a24e9920d23dc57579f4d703e21b05769 100644 (file)
@@ -24,7 +24,8 @@
 
 namespace dnsdist::rules
 {
-GlobalStateHolder<std::vector<RuleAction>> g_ruleactions;
+GlobalStateHolder<std::vector<RuleAction>> s_ruleActions;
+GlobalStateHolder<std::vector<RuleAction>> s_cacheMissRuleActions;
 GlobalStateHolder<std::vector<ResponseRuleAction>> s_respruleactions;
 GlobalStateHolder<std::vector<ResponseRuleAction>> s_cachehitrespruleactions;
 GlobalStateHolder<std::vector<ResponseRuleAction>> s_selfansweredrespruleactions;
@@ -48,4 +49,19 @@ GlobalStateHolder<std::vector<ResponseRuleAction>>& getResponseRuleChainHolder(R
 {
   return s_responseRuleChains.at(static_cast<size_t>(chain)).holder;
 }
+
+static const std::vector<RuleChainDescription> s_ruleChains{
+  {"", "rules", s_ruleActions},
+  {"CacheMiss", "cache-miss-rules", s_cacheMissRuleActions},
+};
+
+const std::vector<RuleChainDescription>& getRuleChains()
+{
+  return s_ruleChains;
+}
+
+GlobalStateHolder<std::vector<RuleAction>>& getRuleChainHolder(RuleChain chain)
+{
+  return s_ruleChains.at(static_cast<size_t>(chain)).holder;
+}
 }
index 0b0ede3d14f44dc5373154170ab680633511a8c9..5d2220cdd5651b92e336be72f6a51c17ebc245a3 100644 (file)
@@ -34,15 +34,6 @@ class DNSResponseAction;
 
 namespace dnsdist::rules
 {
-enum class ResponseRuleChain : uint8_t
-{
-  ResponseRules = 0,
-  CacheHitResponseRules = 1,
-  CacheInsertedResponseRules = 2,
-  SelfAnsweredResponseRules = 3,
-  XFRResponseRules = 4,
-};
-
 struct RuleAction
 {
   std::shared_ptr<DNSRule> d_rule;
@@ -52,6 +43,22 @@ struct RuleAction
   uint64_t d_creationOrder;
 };
 
+struct RuleChainDescription
+{
+  std::string prefix;
+  std::string metricName;
+  GlobalStateHolder<std::vector<RuleAction>>& holder;
+};
+
+enum class RuleChain : uint8_t
+{
+  Rules = 0,
+  CacheMissRules = 1,
+};
+
+const std::vector<RuleChainDescription>& getRuleChains();
+GlobalStateHolder<std::vector<RuleAction>>& getRuleChainHolder(RuleChain chain);
+
 struct ResponseRuleAction
 {
   std::shared_ptr<DNSRule> d_rule;
@@ -61,6 +68,15 @@ struct ResponseRuleAction
   uint64_t d_creationOrder;
 };
 
+enum class ResponseRuleChain : uint8_t
+{
+  ResponseRules = 0,
+  CacheHitResponseRules = 1,
+  CacheInsertedResponseRules = 2,
+  SelfAnsweredResponseRules = 3,
+  XFRResponseRules = 4,
+};
+
 struct ResponseRuleChainDescription
 {
   std::string prefix;
@@ -68,8 +84,6 @@ struct ResponseRuleChainDescription
   GlobalStateHolder<std::vector<ResponseRuleAction>>& holder;
 };
 
-extern GlobalStateHolder<std::vector<RuleAction>> g_ruleactions;
-
 const std::vector<ResponseRuleChainDescription>& getResponseRuleChains();
 GlobalStateHolder<std::vector<ResponseRuleAction>>& getResponseRuleChainHolder(ResponseRuleChain chain);
 
index 696915017fbb3bf1d15a463d49a14da7d91ced24..7e0fd1fb447a585051f5c67499182df0b287241c 100644 (file)
@@ -892,7 +892,9 @@ static void handlePrometheus(const YaHTTP::Request& req, YaHTTP::Response& resp)
 
   output << "# HELP dnsdist_rule_hits " << "Number of hits of that rule" << "\n";
   output << "# TYPE dnsdist_rule_hits " << "counter" << "\n";
-  addRulesToPrometheusOutput(output, dnsdist::rules::g_ruleactions);
+  for (const auto& chain : dnsdist::rules::getRuleChains()) {
+    addRulesToPrometheusOutput(output, chain.holder);
+  }
   for (const auto& chain : dnsdist::rules::getResponseRuleChains()) {
     addRulesToPrometheusOutput(output, chain.holder);
   }
@@ -1265,27 +1267,6 @@ static void handleStats(const YaHTTP::Request& req, YaHTTP::Response& resp)
     }
   }
 
-  Json::array rules;
-  /* unfortunately DNSActions have getStats(),
-     and DNSResponseActions do not. */
-  {
-    auto localRules = dnsdist::rules::g_ruleactions.getLocal();
-    num = 0;
-    rules.reserve(localRules->size());
-    for (const auto& lrule : *localRules) {
-      Json::object rule{
-        {"id", num++},
-        {"creationOrder", (double)lrule.d_creationOrder},
-        {"uuid", boost::uuids::to_string(lrule.d_id)},
-        {"name", lrule.d_name},
-        {"matches", (double)lrule.d_rule->d_matches},
-        {"rule", lrule.d_rule->toString()},
-        {"action", lrule.d_action->toString()},
-        {"action-stats", lrule.d_action->getStats()}};
-      rules.emplace_back(std::move(rule));
-    }
-  }
-
   string acl;
   {
     auto aclEntries = g_ACL.getLocal()->toStringVector();
@@ -1320,12 +1301,33 @@ static void handleStats(const YaHTTP::Request& req, YaHTTP::Response& resp)
                               {"servers", std::move(servers)},
                               {"frontends", std::move(frontends)},
                               {"pools", std::move(pools)},
-                              {"rules", std::move(rules)},
                               {"acl", std::move(acl)},
                               {"local", std::move(localaddressesStr)},
                               {"dohFrontends", std::move(dohs)},
                               {"statistics", std::move(stats)}};
 
+  /* unfortunately DNSActions have getStats(),
+     and DNSResponseActions do not. */
+  for (const auto& chain : dnsdist::rules::getRuleChains()) {
+    Json::array rules;
+    auto localRules = chain.holder.getLocal();
+    num = 0;
+    rules.reserve(localRules->size());
+    for (const auto& lrule : *localRules) {
+      Json::object rule{
+        {"id", num++},
+        {"creationOrder", (double)lrule.d_creationOrder},
+        {"uuid", boost::uuids::to_string(lrule.d_id)},
+        {"name", lrule.d_name},
+        {"matches", (double)lrule.d_rule->d_matches},
+        {"rule", lrule.d_rule->toString()},
+        {"action", lrule.d_action->toString()},
+        {"action-stats", lrule.d_action->getStats()}};
+      rules.emplace_back(std::move(rule));
+    }
+    responseObject[chain.metricName] = std::move(rules);
+  }
+
   for (const auto& chain : dnsdist::rules::getResponseRuleChains()) {
     auto responseRules = someResponseRulesToJson(&chain.holder);
     responseObject[chain.metricName] = std::move(responseRules);
index 70fb23cc4baab7f5796bd88a71178f457e66e8d7..4de6f6eae5cbb6465ef6032d531c7066b7aed5f5 100644 (file)
@@ -1050,7 +1050,28 @@ bool processRulesResult(const DNSAction::Action& action, DNSQuestion& dnsQuestio
   return false;
 }
 
-static bool applyRulesToQuery(LocalHolders& holders, DNSQuestion& dnsQuestion, const struct timespec& now)
+static bool applyRulesChainToQuery(const std::vector<dnsdist::rules::RuleAction>& rules, DNSQuestion& dnsQuestion)
+{
+  DNSAction::Action action = DNSAction::Action::None;
+  string ruleresult;
+  bool drop = false;
+
+  for (const auto& rule : rules) {
+    if (!rule.d_rule->matches(&dnsQuestion)) {
+      continue;
+    }
+
+    rule.d_rule->d_matches++;
+    action = (*rule.d_action)(&dnsQuestion, &ruleresult);
+    if (processRulesResult(action, dnsQuestion, ruleresult, drop)) {
+      break;
+    }
+  }
+
+  return !drop;
+}
+
+static bool applyRulesToQuery(LocalHolders& holders, DNSQuestion& dnsQuestion, const timespec& now)
 {
   if (g_rings.shouldRecordQueries()) {
     g_rings.insertQuery(now, dnsQuestion.ids.origRemote, dnsQuestion.ids.qname, dnsQuestion.ids.qtype, dnsQuestion.getData().size(), *dnsQuestion.getHeader(), dnsQuestion.getProtocol());
@@ -1211,20 +1232,7 @@ static bool applyRulesToQuery(LocalHolders& holders, DNSQuestion& dnsQuestion, c
   }
 #endif /* DISABLE_DYNBLOCKS */
 
-  DNSAction::Action action = DNSAction::Action::None;
-  string ruleresult;
-  bool drop = false;
-  for (const auto& rule : *holders.ruleactions) {
-    if (rule.d_rule->matches(&dnsQuestion)) {
-      rule.d_rule->d_matches++;
-      action = (*rule.d_action)(&dnsQuestion, &ruleresult);
-      if (processRulesResult(action, dnsQuestion, ruleresult, drop)) {
-        break;
-      }
-    }
-  }
-
-  return !drop;
+  return applyRulesChainToQuery(*holders.ruleactions, dnsQuestion);
 }
 
 ssize_t udpClientSendRequestToBackend(const std::shared_ptr<DownstreamState>& backend, const int socketDesc, const PacketBuffer& request, bool healthCheck)
@@ -1416,39 +1424,49 @@ static bool prepareOutgoingResponse(LocalHolders& holders, const ClientState& cl
   return true;
 }
 
+static ProcessQueryResult handleQueryTurnedIntoSelfAnsweredResponse(DNSQuestion& dnsQuestion, LocalHolders& holders)
+{
+  fixUpQueryTurnedResponse(dnsQuestion, dnsQuestion.ids.origFlags);
+
+  if (!prepareOutgoingResponse(holders, *dnsQuestion.ids.cs, dnsQuestion, false)) {
+    return ProcessQueryResult::Drop;
+  }
+
+  const auto rcode = dnsQuestion.getHeader()->rcode;
+  if (rcode == RCode::NXDomain) {
+    ++dnsdist::metrics::g_stats.ruleNXDomain;
+  }
+  else if (rcode == RCode::Refused) {
+    ++dnsdist::metrics::g_stats.ruleRefused;
+  }
+  else if (rcode == RCode::ServFail) {
+    ++dnsdist::metrics::g_stats.ruleServFail;
+  }
+
+  ++dnsdist::metrics::g_stats.selfAnswered;
+  ++dnsQuestion.ids.cs->responses;
+  return ProcessQueryResult::SendAnswer;
+}
+
+static void selectBackendForOutgoingQuery(DNSQuestion& dnsQuestion, const std::shared_ptr<ServerPool>& serverPool, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend)
+{
+  std::shared_ptr<ServerPolicy> poolPolicy = serverPool->policy;
+  const auto& policy = poolPolicy != nullptr ? *poolPolicy : *(holders.policy);
+  const auto servers = serverPool->getServers();
+  selectedBackend = policy.getSelectedBackend(*servers, dnsQuestion);
+}
+
 ProcessQueryResult processQueryAfterRules(DNSQuestion& dnsQuestion, LocalHolders& holders, std::shared_ptr<DownstreamState>& selectedBackend)
 {
   const uint16_t queryId = ntohs(dnsQuestion.getHeader()->id);
 
   try {
     if (dnsQuestion.getHeader()->qr) { // something turned it into a response
-      fixUpQueryTurnedResponse(dnsQuestion, dnsQuestion.ids.origFlags);
-
-      if (!prepareOutgoingResponse(holders, *dnsQuestion.ids.cs, dnsQuestion, false)) {
-        return ProcessQueryResult::Drop;
-      }
-
-      const auto rcode = dnsQuestion.getHeader()->rcode;
-      if (rcode == RCode::NXDomain) {
-        ++dnsdist::metrics::g_stats.ruleNXDomain;
-      }
-      else if (rcode == RCode::Refused) {
-        ++dnsdist::metrics::g_stats.ruleRefused;
-      }
-      else if (rcode == RCode::ServFail) {
-        ++dnsdist::metrics::g_stats.ruleServFail;
-      }
-
-      ++dnsdist::metrics::g_stats.selfAnswered;
-      ++dnsQuestion.ids.cs->responses;
-      return ProcessQueryResult::SendAnswer;
+      return handleQueryTurnedIntoSelfAnsweredResponse(dnsQuestion, holders);
     }
     std::shared_ptr<ServerPool> serverPool = getPool(*holders.pools, dnsQuestion.ids.poolName);
-    std::shared_ptr<ServerPolicy> poolPolicy = serverPool->policy;
     dnsQuestion.ids.packetCache = serverPool->packetCache;
-    const auto& policy = poolPolicy != nullptr ? *poolPolicy : *(holders.policy);
-    const auto servers = serverPool->getServers();
-    selectedBackend = policy.getSelectedBackend(*servers, dnsQuestion);
+    selectBackendForOutgoingQuery(dnsQuestion, serverPool, holders, selectedBackend);
 
     uint32_t allowExpired = selectedBackend ? 0 : g_staleCacheEntriesTTL;
 
@@ -1527,6 +1545,21 @@ ProcessQueryResult processQueryAfterRules(DNSQuestion& dnsQuestion, LocalHolders
       vinfolog("Packet cache miss for query for %s|%s from %s (%s, %d bytes)", dnsQuestion.ids.qname.toLogString(), QType(dnsQuestion.ids.qtype).toString(), dnsQuestion.ids.origRemote.toStringWithPort(), dnsQuestion.ids.protocol.toString(), dnsQuestion.getData().size());
 
       ++dnsdist::metrics::g_stats.cacheMisses;
+
+      const auto existingPool = dnsQuestion.ids.poolName;
+      if (!applyRulesChainToQuery(*holders.cacheMissRuleActions, dnsQuestion)) {
+        return ProcessQueryResult::Drop;
+      }
+      if (dnsQuestion.getHeader()->qr) { // something turned it into a response
+        return handleQueryTurnedIntoSelfAnsweredResponse(dnsQuestion, holders);
+      }
+      /* let's be nice and allow the selection of a different pool,
+         but no second cache-lookup for you */
+      if (dnsQuestion.ids.poolName != existingPool) {
+        serverPool = getPool(*holders.pools, dnsQuestion.ids.poolName);
+        dnsQuestion.ids.packetCache = serverPool->packetCache;
+        selectBackendForOutgoingQuery(dnsQuestion, serverPool, holders, selectedBackend);
+      }
     }
 
     if (!selectedBackend) {
@@ -2743,7 +2776,9 @@ static void cleanupLuaObjects()
 {
   /* when our coverage mode is enabled, we need to make sure
      that the Lua objects are destroyed before the Lua contexts. */
-  dnsdist::rules::g_ruleactions.setState({});
+  for (const auto& chain : dnsdist::rules::getRuleChains()) {
+    chain.holder.setState({});
+  }
   for (const auto& chain : dnsdist::rules::getResponseRuleChains()) {
     chain.holder.setState({});
   }
index 3aca751af53ecb5e4e3e6ec82faa3da4513a1f8b..561e8f68c1f7807e06dba28d1dc412e02470c05b 100644 (file)
@@ -1225,13 +1225,14 @@ enum class ProcessQueryResult : uint8_t
 struct LocalHolders
 {
   LocalHolders() :
-    acl(g_ACL.getLocal()), policy(g_policy.getLocal()), ruleactions(dnsdist::rules::g_ruleactions.getLocal()), cacheHitRespRuleactions(dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::CacheHitResponseRules).getLocal()), cacheInsertedRespRuleActions(dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::CacheInsertedResponseRules).getLocal()), selfAnsweredRespRuleactions(dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::SelfAnsweredResponseRules).getLocal()), servers(g_dstates.getLocal()), dynNMGBlock(g_dynblockNMG.getLocal()), dynSMTBlock(g_dynblockSMT.getLocal()), pools(g_pools.getLocal())
+    acl(g_ACL.getLocal()), policy(g_policy.getLocal()), ruleactions(dnsdist::rules::getRuleChainHolder(dnsdist::rules::RuleChain::Rules).getLocal()), cacheMissRuleActions(dnsdist::rules::getRuleChainHolder(dnsdist::rules::RuleChain::CacheMissRules).getLocal()), cacheHitRespRuleactions(dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::CacheHitResponseRules).getLocal()), cacheInsertedRespRuleActions(dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::CacheInsertedResponseRules).getLocal()), selfAnsweredRespRuleactions(dnsdist::rules::getResponseRuleChainHolder(dnsdist::rules::ResponseRuleChain::SelfAnsweredResponseRules).getLocal()), servers(g_dstates.getLocal()), dynNMGBlock(g_dynblockNMG.getLocal()), dynSMTBlock(g_dynblockSMT.getLocal()), pools(g_pools.getLocal())
   {
   }
 
   LocalStateHolder<NetmaskGroup> acl;
   LocalStateHolder<ServerPolicy> policy;
   LocalStateHolder<vector<dnsdist::rules::RuleAction>> ruleactions;
+  LocalStateHolder<vector<dnsdist::rules::RuleAction>> cacheMissRuleActions;
   LocalStateHolder<vector<dnsdist::rules::ResponseRuleAction>> cacheHitRespRuleactions;
   LocalStateHolder<vector<dnsdist::rules::ResponseRuleAction>> cacheInsertedRespRuleActions;
   LocalStateHolder<vector<dnsdist::rules::ResponseRuleAction>> selfAnsweredRespRuleactions;
index ba9e0f40692b14d7f61af6f3c5740485229f6bdf..3c70c04bff66d2a3dec734e8fc39ba2452b88a65 100644 (file)
@@ -36,79 +36,84 @@ For Rules related to the incoming query:
 
   :param int n: The rule number
 
-.. function:: getCacheHitResponseRule(selector) -> DNSDistResponseRuleAction
+.. function:: getRule(selector) -> DNSDistRuleAction
 
   .. versionadded:: 1.9.0
 
-  Return the cache-hit response rule corresponding to the selector, if any.
+  Return the rule corresponding to the selector, if any.
   The selector can be the position of the rule in the list, as an integer,
   its name as a string or its UUID as a string as well.
 
   :param int or str selector: The position in the list, name or UUID of the rule to return.
 
-.. function:: getCacheInsertedResponseRule(selector) -> DNSDistResponseRuleAction
+.. function:: mvRule(from, to)
 
-  .. versionadded:: 1.9.0
+  Move rule ``from`` to a position where it is in front of ``to``.
+  ``to`` can be one larger than the largest rule, in which case the rule will be moved to the last position.
 
-  Return the cache-inserted response rule corresponding to the selector, if any.
-  The selector can be the position of the rule in the list, as an integer,
-  its name as a string or its UUID as a string as well.
+  :param int from: Rule number to move
+  :param int to: Location to more the Rule to
 
-  :param int or str selector: The position in the list, name or UUID of the rule to return.
+.. function:: mvRuleToTop()
 
-.. function:: getResponseRule(selector) -> DNSDistResponseRuleAction
+  .. versionadded:: 1.6.0
 
-  .. versionadded:: 1.9.0
+  This function moves the last rule to the first position. Before 1.6.0 this was handled by :func:`topRule`.
 
-  Return the response rule corresponding to the selector, if any.
-  The selector can be the position of the rule in the list, as an integer,
-  its name as a string or its UUID as a string as well.
+.. function:: setRules(rules)
 
-  :param int or str selector: The position in the list, name or UUID of the rule to return.
+  Replace the current rules with the supplied list of pairs of DNS Rules and DNS Actions (see :func:`newRuleAction`)
 
-.. function:: getRule(selector) -> DNSDistRuleAction
+  :param [RuleAction] rules: A list of RuleActions
 
-  .. versionadded:: 1.9.0
+.. function:: showRules([options])
 
-  Return the rule corresponding to the selector, if any.
-  The selector can be the position of the rule in the list, as an integer,
-  its name as a string or its UUID as a string as well.
+  Show all defined rules for queries, optionally displaying their UUIDs.
 
-  :param int or str selector: The position in the list, name or UUID of the rule to return.
+  :param table options: A table with key: value pairs with display options.
 
-.. function:: getSelfAnsweredResponseRule(selector) -> DNSDistResponseRuleAction
+  Options:
 
-  .. versionadded:: 1.9.0
+  * ``showUUIDs=false``: bool - Whether to display the UUIDs, defaults to false.
+  * ``truncateRuleWidth=-1``: int - Truncate rules output to ``truncateRuleWidth`` size. Defaults to ``-1`` to display the full rule.
 
-  Return the self-answered response rule corresponding to the selector, if any.
-  The selector can be the position of the rule in the list, as an integer,
-  its name as a string or its UUID as a string as well.
+.. function:: topRule()
 
-  :param int or str selector: The position in the list, name or UUID of the rule to return.
+  .. versionchanged:: 1.6.0
+    Replaced by :func:`mvRuleToTop`
 
-.. function:: mvRule(from, to)
+  Before 1.6.0 this function used to move the last rule to the first position, which is now handled by :func:`mvRuleToTop`.
 
-  Move rule ``from`` to a position where it is in front of ``to``.
-  ``to`` can be one larger than the largest rule, in which case the rule will be moved to the last position.
+.. function:: rmRule(id)
 
-  :param int from: Rule number to move
-  :param int to: Location to more the Rule to
+  .. versionchanged:: 1.6.0
+    ``id`` can now be a string representing the name of the rule.
 
-.. function:: mvRuleToTop()
+  Remove rule ``id``.
 
-  .. versionadded:: 1.6.0
+  :param int id: The position of the rule to remove if ``id`` is numerical, its UUID or name otherwise
 
-  This function moves the last rule to the first position. Before 1.6.0 this was handled by :func:`topRule`.
+Cache misses
+------------
 
-.. function:: newRuleAction(rule, action[, options])
+For Rules related to the incoming query after a cache miss:
 
-  .. versionchanged:: 1.6.0
-    Added ``name`` to the ``options``.
+.. warning::
+  While all selectors and actions are available, some actions will no longer be honored at
+  this point. For example changing the backend pool will not trigger a second cache-lookup.
+  Switching from a backend pool that has EDNS Client Subnet enabled to one that doesn't
+  will result in the EDNS Client Subnet corresponding to the initial server pool to be
+  added to the query.
 
-  Return a pair of DNS Rule and DNS Action, to be used with :func:`setRules`.
+.. function:: addCacheMissAction(DNSrule, action [, options])
 
-  :param Rule rule: A Rule (see :doc:`selectors`)
-  :param Action action: The Action (see :doc:`actions`) to apply to the matched traffic
+  .. versionadded:: 1.10
+
+  Add a Rule and Action to the existing cache miss rules.
+  If a string (or list of) is passed as the first parameter instead of a :class:`DNSRule`, it behaves as if the string or list of strings was passed to :func:`NetmaskGroupRule` or :func:`SuffixMatchNodeRule`.
+
+  :param DNSrule rule: A :class:`DNSRule`, e.g. an :func:`AllRule`, or a compounded bunch of rules using e.g. :func:`AndRule`.
+  :param action: The action to take
   :param table options: A table with key: value pairs with options.
 
   Options:
@@ -116,15 +121,59 @@ For Rules related to the incoming query:
   * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
   * ``name``: string - Name to assign to the new rule.
 
-.. function:: setRules(rules)
+.. function:: clearCacheMissRules()
 
-  Replace the current rules with the supplied list of pairs of DNS Rules and DNS Actions (see :func:`newRuleAction`)
+  .. versionadded:: 1.10
+
+  Remove all current cache miss rules.
+
+.. function:: getCacheMissAction(n) -> DNSDistRuleAction
+
+  .. versionadded:: 1.10
+
+  Returns the :class:`DNSDistRuleAction` associated with cache miss rule ``n``.
+
+  :param int n: The rule number
+
+.. function:: getCacheMissRule(selector) -> DNSDistRuleAction
+
+  .. versionadded:: 1.10
+
+  Return the cache miss rule corresponding to the selector, if any.
+  The selector can be the position of the rule in the list, as an integer,
+  its name as a string or its UUID as a string as well.
+
+  :param int or str selector: The position in the list, name or UUID of the rule to return.
+
+.. function:: mvCacheMissRule(from, to)
+
+  .. versionadded:: 1.10
+
+  Move cache miss rule ``from`` to a position where it is in front of ``to``.
+  ``to`` can be one larger than the largest rule, in which case the rule will be moved to the last position.
+
+  :param int from: Rule number to move
+  :param int to: Location to more the Rule to
+
+.. function:: mvCacheMissRuleToTop()
+
+  .. versionadded:: 1.10
+
+  This function moves the last cache miss rule to the first position.
+
+.. function:: setCacheMissRules(rules)
+
+  .. versionadded:: 1.10
+
+  Replace the current cache miss rules with the supplied list of pairs of DNS Rules and DNS Actions (see :func:`newRuleAction`)
 
   :param [RuleAction] rules: A list of RuleActions
 
-.. function:: showRules([options])
+.. function:: showCacheMissRules([options])
 
-  Show all defined rules for queries, optionally displaying their UUIDs.
+  .. versionadded:: 1.10
+
+  Show all defined cache miss rules for queries, optionally displaying their UUIDs.
 
   :param table options: A table with key: value pairs with display options.
 
@@ -133,21 +182,13 @@ For Rules related to the incoming query:
   * ``showUUIDs=false``: bool - Whether to display the UUIDs, defaults to false.
   * ``truncateRuleWidth=-1``: int - Truncate rules output to ``truncateRuleWidth`` size. Defaults to ``-1`` to display the full rule.
 
-.. function:: topRule()
-
-  .. versionchanged:: 1.6.0
-    Replaced by :func:`mvRuleToTop`
-
-  Before 1.6.0 this function used to move the last rule to the first position, which is now handled by :func:`mvRuleToTop`.
-
-.. function:: rmRule(id)
+.. function:: rmCacheMissRule(id)
 
-  .. versionchanged:: 1.6.0
-    ``id`` can now be a string representing the name of the rule.
+  .. versionadded:: 1.10
 
   Remove rule ``id``.
 
-  :param int id: The position of the rule to remove if ``id`` is numerical, its UUID or name otherwise
+  :param int id: The position of the cache miss rule to remove if ``id`` is numerical, its UUID or name otherwise
 
 Responses
 ---------
@@ -174,6 +215,22 @@ For Rules related to responses:
   * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
   * ``name``: string - Name to assign to the new rule.
 
+.. function:: clearResponseRules()
+
+  .. versionadded:: 1.10
+
+  Remove all current response rules.
+
+.. function:: getResponseRule(selector) -> DNSDistResponseRuleAction
+
+  .. versionadded:: 1.9.0
+
+  Return the response rule corresponding to the selector, if any.
+  The selector can be the position of the rule in the list, as an integer,
+  its name as a string or its UUID as a string as well.
+
+  :param int or str selector: The position in the list, name or UUID of the rule to return.
+
 .. function:: mvResponseRule(from, to)
 
   Move response rule ``from`` to a position where it is in front of ``to``.
@@ -240,6 +297,22 @@ Functions for manipulating Cache Hit Response Rules:
   * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
   * ``name``: string - Name to assign to the new rule.
 
+.. function:: clearCacheHitResponseRules()
+
+  .. versionadded:: 1.10
+
+  Remove all current cache-hit response rules.
+
+.. function:: getCacheHitResponseRule(selector) -> DNSDistResponseRuleAction
+
+  .. versionadded:: 1.9.0
+
+  Return the cache-hit response rule corresponding to the selector, if any.
+  The selector can be the position of the rule in the list, as an integer,
+  its name as a string or its UUID as a string as well.
+
+  :param int or str selector: The position in the list, name or UUID of the rule to return.
+
 .. function:: mvCacheHitResponseRule(from, to)
 
   Move cache hit response rule ``from`` to a position where it is in front of ``to``.
@@ -303,6 +376,22 @@ Functions for manipulating Cache Inserted Response Rules:
   * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
   * ``name``: string - Name to assign to the new rule.
 
+.. function:: clearCacheInsertedResponseRules()
+
+  .. versionadded:: 1.10
+
+  Remove all current cache-inserted response rules.
+
+.. function:: getCacheInsertedResponseRule(selector) -> DNSDistResponseRuleAction
+
+  .. versionadded:: 1.9.0
+
+  Return the cache-inserted response rule corresponding to the selector, if any.
+  The selector can be the position of the rule in the list, as an integer,
+  its name as a string or its UUID as a string as well.
+
+  :param int or str selector: The position in the list, name or UUID of the rule to return.
+
 .. function:: mvCacheInsertedResponseRule(from, to)
 
   .. versionadded:: 1.8.0
@@ -363,6 +452,22 @@ Functions for manipulating Self-Answered Response Rules:
   * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
   * ``name``: string - Name to assign to the new rule.
 
+.. function:: clearSelfAnsweredResponseRules()
+
+  .. versionadded:: 1.10
+
+  Remove all current self-answered response rules.
+
+.. function:: getSelfAnsweredResponseRule(selector) -> DNSDistResponseRuleAction
+
+  .. versionadded:: 1.9.0
+
+  Return the self-answered response rule corresponding to the selector, if any.
+  The selector can be the position of the rule in the list, as an integer,
+  its name as a string or its UUID as a string as well.
+
+  :param int or str selector: The position in the list, name or UUID of the rule to return.
+
 .. function:: mvSelfAnsweredResponseRule(from, to)
 
   Move self answered response rule ``from`` to a position where it is in front of ``to``.
@@ -495,3 +600,19 @@ Convenience Functions
   ``makeRule("0.0.0.0/0")`` will for example match all IPv4 traffic, ``makeRule({"be","nl","lu"})`` will match all Benelux DNS traffic.
 
   :param string rule: A string, or list of strings, to convert to a rule.
+
+.. function:: newRuleAction(rule, action[, options])
+
+  .. versionchanged:: 1.6.0
+    Added ``name`` to the ``options``.
+
+  Return a pair of DNS Rule and DNS Action, to be used with :func:`setRules`.
+
+  :param Rule rule: A Rule (see :doc:`selectors`)
+  :param Action action: The Action (see :doc:`actions`) to apply to the matched traffic
+  :param table options: A table with key: value pairs with options.
+
+  Options:
+
+  * ``uuid``: string - UUID to assign to the new rule. By default a random UUID is generated for each rule.
+  * ``name``: string - Name to assign to the new rule.
index 3566d61c5096849cb9b64e13f6926fad202091fc..db9238d770441fb3e3673d7b70bb5958eb84a106 100644 (file)
@@ -35,7 +35,6 @@
 #include "dnsdist-tcp-upstream.hh"
 
 GlobalStateHolder<NetmaskGroup> g_ACL;
-GlobalStateHolder<std::vector<dnsdist::rules::RuleAction> > g_ruleactions;
 GlobalStateHolder<servers_t> g_dstates;
 
 QueryCount g_qcount;