]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Enhancement to support rule action for query timeout case
authorOliver Chen <oliver.chen@nokia-sbell.com>
Thu, 3 Apr 2025 02:40:45 +0000 (02:40 +0000)
committerOliver Chen <oliver.chen@nokia-sbell.com>
Thu, 3 Apr 2025 02:43:21 +0000 (02:43 +0000)
The dnsdist already supports all types of error response code rule action
except timeout. Users may want to use the same feature for timeout case.

19 files changed:
pdns/dnsdistdist/dnsdist-backend.cc
pdns/dnsdistdist/dnsdist-configuration-yaml.cc
pdns/dnsdistdist/dnsdist-console.cc
pdns/dnsdistdist/dnsdist-doh-common.hh
pdns/dnsdistdist/dnsdist-lua-bindings-dnsquestion.cc
pdns/dnsdistdist/dnsdist-nghttp2-in.cc
pdns/dnsdistdist/dnsdist-nghttp2.cc
pdns/dnsdistdist/dnsdist-rule-chains.cc
pdns/dnsdistdist/dnsdist-rule-chains.hh
pdns/dnsdistdist/dnsdist-rust-lib/rust-post-in.rs
pdns/dnsdistdist/dnsdist-rust-lib/rust/src/lib.rs
pdns/dnsdistdist/dnsdist-settings-definitions.yml
pdns/dnsdistdist/dnsdist-tcp-downstream.cc
pdns/dnsdistdist/dnsdist.cc
pdns/dnsdistdist/dnsdist.hh
pdns/dnsdistdist/docs/reference/rules-management.rst
pdns/dnsdistdist/docs/reference/yaml-settings.rst
pdns/dnsdistdist/doh.cc
pdns/dnsdistdist/test-dnsdist_cc.cc

index 33bcc7b6f58267f0f167abbe6f98368a73164186..9daae424cc68c9ce0268dcf0ac4a2ca0211601ed 100644 (file)
@@ -442,7 +442,6 @@ void DownstreamState::handleUDPTimeout(IDState& ids)
 {
   ids.age = 0;
   ids.inUse = false;
-  DOHUnitInterface::handleTimeout(std::move(ids.internal.du));
   ++reuseds;
   --outstanding;
   ++dnsdist::metrics::g_stats.downstreamTimeouts; // this is an 'actively' discovered timeout
@@ -450,6 +449,13 @@ void DownstreamState::handleUDPTimeout(IDState& ids)
            d_config.remote.toStringWithPort(), getName(),
            ids.internal.qname.toLogString(), QType(ids.internal.qtype).toString(), ids.internal.origRemote.toStringWithPort());
 
+  const auto& chains = dnsdist::configuration::getCurrentRuntimeConfiguration().d_ruleChains;
+  const auto& timeoutRespRules = dnsdist::rules::getResponseRuleChain(chains, dnsdist::rules::ResponseRuleChain::TimeoutResponseRules);
+  auto sender = ids.internal.du == nullptr ? nullptr : ids.internal.du->getQuerySender();
+  if (!handleTimeoutResponseRules(timeoutRespRules, ids.internal, shared_from_this(), std::move(sender))) {
+    DOHUnitInterface::handleTimeout(std::move(ids.internal.du));
+  }
+
   if (g_rings.shouldRecordResponses()) {
     struct timespec ts;
     gettime(&ts);
index 7f0e633d9420872e4d64291b26b39f28eaeca6bd..0d8200f08cab16d7af8ecabc4ef16cf09be80faa 100644 (file)
@@ -542,6 +542,11 @@ static void loadRulesConfiguration(const dnsdist::rust::settings::GlobalConfigur
       boost::uuids::uuid ruleUniqueID = rule.uuid.empty() ? getUniqueID() : getUniqueID(std::string(rule.uuid));
       dnsdist::rules::add(config.d_ruleChains, dnsdist::rules::ResponseRuleChain::XFRResponseRules, std::move(rule.selector.selector->d_rule), rule.action.action->d_action, std::string(rule.name), ruleUniqueID, 0);
     }
+
+    for (const auto& rule : globalConfig.timeout_response_rules) {
+      boost::uuids::uuid ruleUniqueID = rule.uuid.empty() ? getUniqueID() : getUniqueID(std::string(rule.uuid));
+      dnsdist::rules::add(config.d_ruleChains, dnsdist::rules::ResponseRuleChain::TimeoutResponseRules, std::move(rule.selector.selector->d_rule), rule.action.action->d_action, std::string(rule.name), ruleUniqueID, 0);
+    }
   });
 }
 
index 55bbb4ce57f6d2b3ae764baf9a9f35b0c2903e74..339b450ca3bee0cde6e361dc4cef2a1042ffe6a3 100644 (file)
@@ -501,6 +501,7 @@ static const std::vector<dnsdist::console::ConsoleKeyword> s_consoleKeywords{
   {"addExitCallback", true, "callback", "register a function to be called when DNSdist exits"},
   {"addResponseAction", true, R"(DNS rule, DNS response action [, {uuid="UUID", name="name"}}])", "add a response rule"},
   {"addSelfAnsweredResponseAction", true, R"(DNS rule, DNS response action [, {uuid="UUID", name="name"}}])", "add a self-answered response rule"},
+  {"addTimeoutResponseAction", true, R"(DNS rule, DNS response action [, {uuid="UUID", name="name"}}])", "add a Timeout response rule"},
   {"addXFRResponseAction", true, R"(DNS rule, DNS response action [, {uuid="UUID", name="name"}}])", "add a XFR response rule"},
   {"addTLSLocal", true, "addr, certFile(s), keyFile(s) [,params]", "listen to incoming DNS over TLS queries on the specified address using the specified certificate (or list of) and key (or list of). The last parameter is a table"},
   {"AllowAction", true, "", "let these packets go through"},
@@ -513,6 +514,7 @@ static const std::vector<dnsdist::console::ConsoleKeyword> s_consoleKeywords{
   {"clearDynBlocks", true, "", "clear all dynamic blocks"},
   {"clearQueryCounters", true, "", "clears the query counter buffer"},
   {"clearRules", true, "", "remove all current rules"},
+  {"clearTimeoutResponseRules", true, "", "remove all current timeout response rules"},
   {"controlSocket", true, "addr", "open a control socket on this address / connect to this address in client mode"},
   {"ContinueAction", true, "action", "execute the specified action and continue the processing of the remaining rules, regardless of the return of the action"},
   {"declareMetric", true, "name, type, description [, prometheusName]", "Declare a custom metric"},
@@ -576,11 +578,13 @@ static const std::vector<dnsdist::console::ConsoleKeyword> s_consoleKeywords{
   {"getServer", true, "id", "returns server with index 'n' or whose uuid matches if 'id' is an UUID string"},
   {"getServers", true, "", "returns a table with all defined servers"},
   {"getStatisticsCounters", true, "", "returns a map of statistic counters"},
+  {"getTimeoutResponseRule", true, "selector", "Return the timeout response rule corresponding to the selector, if any"},
   {"getTopCacheHitResponseRules", true, "[top]", "return the `top` cache-hit response rules"},
   {"getTopCacheInsertedResponseRules", true, "[top]", "return the `top` cache-inserted response rules"},
   {"getTopResponseRules", true, "[top]", "return the `top` response rules"},
   {"getTopRules", true, "[top]", "return the `top` rules"},
   {"getTopSelfAnsweredResponseRules", true, "[top]", "return the `top` self-answered response rules"},
+  {"getTopTimeoutResponseRules", true, "[top]", "return the `top` Timeout response rules"},
   {"getTopXFRResponseRules", true, "[top]", "return the `top` XFR response rules"},
   {"getTLSFrontend", true, "n", "returns the TLS frontend with index n"},
   {"getTLSFrontendCount", true, "", "returns the number of DoT listeners"},
@@ -637,6 +641,8 @@ static const std::vector<dnsdist::console::ConsoleKeyword> s_consoleKeywords{
   {"mvRuleToTop", true, "", "move the last rule to the first position"},
   {"mvSelfAnsweredResponseRule", true, "from, to", "move self-answered response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule"},
   {"mvSelfAnsweredResponseRuleToTop", true, "", "move the last self-answered response rule to the first position"},
+  {"mvTimeoutResponseRule", true, "from, to", "move timeout response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule"},
+  {"mvTimeoutResponseRuleToTop", true, "", "move the last Timeout response rule to the first position"},
   {"mvXFRResponseRule", true, "from, to", "move XFR response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule"},
   {"mvXFRResponseRuleToTop", true, "", "move the last XFR response rule to the first position"},
   {"NetmaskGroupRule", true, "nmg[, src]", "Matches traffic from/to the network range specified in nmg. Set the src parameter to false to match nmg against destination address instead of source address. This can be used to differentiate between clients"},
@@ -699,6 +705,7 @@ static const std::vector<dnsdist::console::ConsoleKeyword> s_consoleKeywords{
   {"rmRule", true, "id", "remove rule in position 'id', or whose uuid matches if 'id' is an UUID string, or finally whose name matches if 'id' is a string but not a valid UUID"},
   {"rmSelfAnsweredResponseRule", true, "id", "remove self-answered response rule in position 'id', or whose uuid matches if 'id' is an UUID string, or finally whose name matches if 'id' is a string but not a valid UUID"},
   {"rmServer", true, "id", "remove server with index 'id' or whose uuid matches if 'id' is an UUID string"},
+  {"rmTimeoutResponseRule", true, "id", "remove Timeout response rule in position 'id', or whose uuid matches if 'id' is an UUID string, or finally whose name matches if 'id' is a string but not a valid UUID"},
   {"rmXFRResponseRule", true, "id", "remove XFR response rule in position 'id', or whose uuid matches if 'id' is an UUID string, or finally whose name matches if 'id' is a string but not a valid UUID"},
   {"roundrobin", false, "", "Simple round robin over available servers"},
   {"sendCustomTrap", true, "str", "send a custom `SNMP` trap from Lua, containing the `str` string"},
@@ -795,6 +802,7 @@ static const std::vector<dnsdist::console::ConsoleKeyword> s_consoleKeywords{
   {"showSelfAnsweredResponseRules", true, "[{showUUIDs=false, truncateRuleWidth=-1}]", "show all defined self-answered response rules, optionally with their UUIDs and optionally truncated to a given width"},
   {"showServerPolicy", true, "", "show name of currently operational server selection policy"},
   {"showServers", true, "[{showUUIDs=false}]", "output all servers, optionally with their UUIDs"},
+  {"showTimeoutResponseRules", true, "[{showUUIDs=false, truncateRuleWidth=-1}]", "show all defined timeout response rules, optionally with their UUIDs and optionally truncated to a given width"},
   {"showTCPStats", true, "", "show some statistics regarding TCP"},
   {"showTLSErrorCounters", true, "", "show metrics about TLS handshake failures"},
   {"showTLSFrontends", true, "", "list all the available TLS contexts"},
@@ -846,6 +854,7 @@ static const std::vector<dnsdist::console::ConsoleKeyword> s_consoleKeywords{
   {"topSelfAnsweredResponseRules", true, "[top][, vars]", "show `top` self-answered response rules"},
   {"topSlow", true, "[top][, limit][, labels]", "show `top` queries slower than `limit` milliseconds (timeouts excepted), grouped by last `labels` labels"},
   {"topTimeouts", true, "[top][, labels]", "show `top` queries that timed out, grouped by last `labels` labels"},
+  {"topTimeoutResponseRules", true, "[top][, vars]", "show `top` timeout response rules"},
   {"TrailingDataRule", true, "", "Matches if the query has trailing data"},
   {"truncateTC", true, "bool", "if set (defaults to no starting with dnsdist 1.2.0) truncate TC=1 answers so they are actually empty. Fixes an issue for PowerDNS Authoritative Server 2.9.22. Note: turning this on breaks compatibility with RFC 6891."},
   {"unregisterDynBPFFilter", true, "DynBPFFilter", "unregister this dynamic BPF filter"},
index 9d0a4669288ffe3db4aa939b76eaa1ee6b83ade7..ae430e056a357a595fdf1af0c2974b6ea4db9180 100644 (file)
@@ -202,6 +202,8 @@ struct DOHFrontend
 
 struct DownstreamState;
 
+class TCPQuerySender;
+
 #ifndef HAVE_DNS_OVER_HTTPS
 struct DOHUnitInterface
 {
@@ -228,6 +230,7 @@ struct DOHUnitInterface
   virtual const std::string& getHTTPHost() const = 0;
   virtual const std::string& getHTTPScheme() const = 0;
   virtual const std::unordered_map<std::string, std::string>& getHTTPHeaders() const = 0;
+  virtual std::shared_ptr<TCPQuerySender> getQuerySender() const = 0;
   virtual void setHTTPResponse(uint16_t statusCode, PacketBuffer&& body, const std::string& contentType = "") = 0;
   virtual void handleTimeout() = 0;
   virtual void handleUDPResponse(PacketBuffer&& response, InternalQueryState&& state, const std::shared_ptr<DownstreamState>&) = 0;
index 5c6530dca0a809c90689254614c0a67738eb03fc..941d24c423352809411e8b42d03947333e27d79d 100644 (file)
@@ -612,7 +612,7 @@ void setupLuaBindingsDNSQuestion([[maybe_unused]] LuaContext& luaCtx)
   });
 
   luaCtx.registerFunction<bool (DNSResponse::*)()>("restart", [](DNSResponse& dnsResponse) {
-    if (!dnsResponse.ids.d_packet) {
+    if (!dnsResponse.ids.d_packet || dnsResponse.ids.d_packet->size() < sizeof(struct dnsheader)) {
       return false;
     }
     dnsResponse.asynchronous = true;
index 8c92b17eea958bf8111b9ccf476dd3bede79b304..d4494a72382dcfe85ee9d69251165249cafa5b87 100644 (file)
@@ -136,6 +136,11 @@ public:
     return *d_query.d_headers;
   }
 
+  [[nodiscard]] std::shared_ptr<TCPQuerySender> getQuerySender() const override
+  {
+    return std::dynamic_pointer_cast<TCPQuerySender>(d_connection.lock());
+  }
+
   void setHTTPResponse(uint16_t statusCode, PacketBuffer&& body, const std::string& contentType = "") override
   {
     d_query.d_statusCode = statusCode;
@@ -203,6 +208,8 @@ void IncomingHTTP2Connection::handleResponse(const struct timeval& now, TCPRespo
       state.forwardedOverUDP = false;
       bool proxyProtocolPayloadAdded = state.d_proxyProtocolPayloadSize > 0;
       auto cpq = getCrossProtocolQuery(std::move(query), std::move(state), response.d_ds);
+      /* 'd_packet' buffer moved by InternalQuery constructor, need re-association */
+      cpq->query.d_idstate.d_packet = std::make_unique<PacketBuffer>(cpq->query.d_buffer);
       cpq->query.d_proxyProtocolPayloadAdded = proxyProtocolPayloadAdded;
       if (g_tcpclientthreads && g_tcpclientthreads->passCrossProtocolQueryToThread(std::move(cpq))) {
         return;
index 5635822224a0a2a64262a6634a79165e7362207f..fdb05d816606635e8f92b47f72d6905e26136d12 100644 (file)
@@ -182,9 +182,16 @@ void DoHConnectionToBackend::handleIOError()
   struct timeval now{
     .tv_sec = 0, .tv_usec = 0};
 
+  const auto& chains = dnsdist::configuration::getCurrentRuntimeConfiguration().d_ruleChains;
+  const auto& timeoutRespRules = dnsdist::rules::getResponseRuleChain(chains, dnsdist::rules::ResponseRuleChain::TimeoutResponseRules);
+
   gettimeofday(&now, nullptr);
   for (auto& request : d_currentStreams) {
-    handleResponseError(std::move(request.second), now);
+    if (!d_healthCheckQuery && handleTimeoutResponseRules(timeoutRespRules, request.second.d_query.d_idstate, d_ds, request.second.d_sender)) {
+      d_ds->reportTimeoutOrError();
+    } else {
+      handleResponseError(std::move(request.second), now);
+    }
   }
 
   d_currentStreams.clear();
index 57e7b74ea06fb23c373c16344c4c6123bfe6eeb5..a3574c4961d4d895a2d8c5b0e1039eb58bc49a99 100644 (file)
@@ -30,6 +30,7 @@ static const std::vector<ResponseRuleChainDescription> s_responseRuleChains{
   {"CacheInserted", "cache-inserted-response-rules", ResponseRuleChain::CacheInsertedResponseRules},
   {"SelfAnswered", "self-answered-response-rules", ResponseRuleChain::SelfAnsweredResponseRules},
   {"XFR", "xfr-response-rules", ResponseRuleChain::XFRResponseRules},
+  {"Timeout", "timeout-response-rules", ResponseRuleChain::TimeoutResponseRules},
 };
 
 const std::vector<ResponseRuleChainDescription>& getResponseRuleChainDescriptions()
@@ -94,6 +95,8 @@ std::vector<ResponseRuleAction>& getResponseRuleChain(RuleChains& chains, Respon
     return chains.d_selfansweredrespruleactions;
   case ResponseRuleChain::XFRResponseRules:
     return chains.d_XFRRespRuleActions;
+  case ResponseRuleChain::TimeoutResponseRules:
+    return chains.d_TimeoutRespRuleActions;
   }
 
   throw std::runtime_error("Trying to accept an invalid response rule chain");
@@ -112,6 +115,8 @@ const std::vector<ResponseRuleAction>& getResponseRuleChain(const RuleChains& ch
     return chains.d_selfansweredrespruleactions;
   case ResponseRuleChain::XFRResponseRules:
     return chains.d_XFRRespRuleActions;
+  case ResponseRuleChain::TimeoutResponseRules:
+    return chains.d_TimeoutRespRuleActions;
   }
 
   throw std::runtime_error("Trying to accept an invalid response rule chain");
index 47657635ca00eaf058562412b9f425d9db0ded9a..13e15a7d1269f248dc75b922907a301296fb78fb 100644 (file)
@@ -71,6 +71,7 @@ enum class ResponseRuleChain : uint8_t
   CacheInsertedResponseRules = 2,
   SelfAnsweredResponseRules = 3,
   XFRResponseRules = 4,
+  TimeoutResponseRules = 5,
 };
 
 struct ResponseRuleChainDescription
@@ -89,6 +90,7 @@ struct RuleChains
   std::vector<ResponseRuleAction> d_selfansweredrespruleactions;
   std::vector<ResponseRuleAction> d_cacheInsertedRespRuleActions;
   std::vector<ResponseRuleAction> d_XFRRespRuleActions;
+  std::vector<ResponseRuleAction> d_TimeoutRespRuleActions;
 };
 
 const std::vector<RuleChainDescription>& getRuleChainDescriptions();
index 1209fe8bb7cacf4a5706630e785f8fe1b4ea2cd4..57a74c880e869e57bb030b58123570a7d6a3619b 100644 (file)
@@ -103,6 +103,7 @@ fn get_global_configuration_from_serde(
     config.cache_inserted_response_rules = get_response_rules_from_serde(&serde.cache_inserted_response_rules);
     config.self_answered_response_rules = get_response_rules_from_serde(&serde.self_answered_response_rules);
     config.xfr_response_rules = get_response_rules_from_serde(&serde.xfr_response_rules);
+    config.timeout_response_rules = get_response_rules_from_serde(&serde.timeout_response_rules);
     config
 }
 
index 9452e1fcdc0e056f6e2cb1de35766d696379e5b6..a02d173ca855160bc0cda9ad6cce4eef0387a160 100644 (file)
@@ -1154,6 +1154,7 @@ mod dnsdistsettings {
         webserver: WebserverConfiguration,
         xfr_response_rules: Vec<ResponseRuleConfiguration>,
         xsk: Vec<XskConfiguration>,
+        timeout_response_rules: Vec<ResponseRuleConfiguration>,
     }
 
     #[derive(Deserialize, Serialize, Debug, PartialEq)]
@@ -2427,6 +2428,8 @@ impl ResponseRuleConfigurationSerde {
         xfr_response_rules: Vec<ResponseRuleConfigurationSerde>,
         #[serde(default, skip_serializing_if = "crate::is_default")]
         xsk: Vec<dnsdistsettings::XskConfiguration>,
+        #[serde(default, skip_serializing_if = "crate::is_default")]
+        timeout_response_rules: Vec<ResponseRuleConfigurationSerde>,
     }
 
 #[derive(Default, Serialize, Deserialize, Debug, PartialEq)]
@@ -3712,6 +3715,9 @@ impl GlobalConfigurationSerde {
     }
         for sub_type in &self.xsk {
         sub_type.validate()?;
+    }
+        for sub_type in &self.timeout_response_rules {
+        sub_type.validate()?;
     }
         Ok(())
     }
@@ -4502,6 +4508,7 @@ fn get_global_configuration_from_serde(
     config.cache_inserted_response_rules = get_response_rules_from_serde(&serde.cache_inserted_response_rules);
     config.self_answered_response_rules = get_response_rules_from_serde(&serde.self_answered_response_rules);
     config.xfr_response_rules = get_response_rules_from_serde(&serde.xfr_response_rules);
+    config.timeout_response_rules = get_response_rules_from_serde(&serde.timeout_response_rules);
     config
 }
 
index 754ccf0cd868d87080fe3830e4e394e39952e03b..935aa04476b3db1d3f0ca651658e4cb598d2b28b 100644 (file)
@@ -142,6 +142,11 @@ global:
       type: "Vec<XskConfiguration>"
       default: true
       description: "List of AF_XDP / XSK objects"
+    - name: "timeout_response_rules"
+      type: "Vec<ResponseRuleConfiguration>"
+      default: true
+      skip-serde: true
+      description: "List of rules executed when a timeout event occurred"
 
 metrics:
   description: "Metrics-related settings"
index cd14a08347f91e847620380722023057767ef0b2..5c1335438b5feb3c14161a27d120d9734604d8e1 100644 (file)
@@ -609,13 +609,18 @@ void TCPConnectionToBackend::notifyAllQueriesFailed(const struct timeval& now, F
     }
   };
 
+  const auto& chains = dnsdist::configuration::getCurrentRuntimeConfiguration().d_ruleChains;
+  const auto& timeoutRespRules = dnsdist::rules::getResponseRuleChain(chains, dnsdist::rules::ResponseRuleChain::TimeoutResponseRules);
+
   try {
     if (d_state == State::sendingQueryToBackend) {
       increaseCounters(d_currentQuery.d_query.d_idstate.cs);
       auto sender = std::move(d_currentQuery.d_sender);
       if (sender->active()) {
-        TCPResponse response(std::move(d_currentQuery.d_query));
-        sender->notifyIOError(now, std::move(response));
+        if (!handleTimeoutResponseRules(timeoutRespRules, d_currentQuery.d_query.d_idstate, d_ds, sender)) {
+          TCPResponse response(std::move(d_currentQuery.d_query));
+          sender->notifyIOError(now, std::move(response));
+        }
       }
     }
 
@@ -623,8 +628,10 @@ void TCPConnectionToBackend::notifyAllQueriesFailed(const struct timeval& now, F
       increaseCounters(query.d_query.d_idstate.cs);
       auto sender = std::move(query.d_sender);
       if (sender->active()) {
-        TCPResponse response(std::move(query.d_query));
-        sender->notifyIOError(now, std::move(response));
+        if (!handleTimeoutResponseRules(timeoutRespRules, query.d_query.d_idstate, d_ds, sender)) {
+          TCPResponse response(std::move(query.d_query));
+          sender->notifyIOError(now, std::move(response));
+        }
       }
     }
 
@@ -632,8 +639,10 @@ void TCPConnectionToBackend::notifyAllQueriesFailed(const struct timeval& now, F
       increaseCounters(response.second.d_query.d_idstate.cs);
       auto sender = std::move(response.second.d_sender);
       if (sender->active()) {
-        TCPResponse tresp(std::move(response.second.d_query));
-        sender->notifyIOError(now, std::move(tresp));
+        if (!handleTimeoutResponseRules(timeoutRespRules, response.second.d_query.d_idstate, d_ds, sender)) {
+          TCPResponse tresp(std::move(response.second.d_query));
+          sender->notifyIOError(now, std::move(tresp));
+        }
       }
     }
   }
index 3ca83043d2c3f855d88b89016c7754a4b6b93075..c068089db23bb875de850217780b15efcb29400e 100644 (file)
@@ -56,6 +56,7 @@
 #include "dnsdist-lua.hh"
 #include "dnsdist-lua-hooks.hh"
 #include "dnsdist-nghttp2.hh"
+#include "dnsdist-nghttp2-in.hh"
 #include "dnsdist-proxy-protocol.hh"
 #include "dnsdist-random.hh"
 #include "dnsdist-rings.hh"
@@ -1563,6 +1564,28 @@ ProcessQueryResult processQueryAfterRules(DNSQuestion& dnsQuestion, std::shared_
   return ProcessQueryResult::Drop;
 }
 
+bool handleTimeoutResponseRules(const std::vector<dnsdist::rules::ResponseRuleAction>& rules, InternalQueryState& ids, std::shared_ptr<DownstreamState> ds, std::shared_ptr<TCPQuerySender> sender)
+{
+  PacketBuffer empty;
+  DNSResponse dnsResponse(ids, empty, ds);
+  auto protocol = dnsResponse.getProtocol();
+
+  vinfolog("Handling timeout response rules for incoming protocol = %s", protocol.toString());
+  if (protocol == dnsdist::Protocol::DoH) {
+    dnsResponse.d_incomingTCPState = std::dynamic_pointer_cast<IncomingHTTP2Connection>(sender);
+    if (!dnsResponse.d_incomingTCPState || !sender || !sender->active()) {
+      return false;
+    }
+  } else if (protocol == dnsdist::Protocol::DoTCP || protocol == dnsdist::Protocol::DNSCryptTCP || protocol == dnsdist::Protocol::DoT) {
+    dnsResponse.d_incomingTCPState = std::dynamic_pointer_cast<IncomingTCPConnectionState>(sender);
+    if (!dnsResponse.d_incomingTCPState || !sender || !sender->active()) {
+      return false;
+    }
+  }
+  (void)applyRulesToResponse(rules, dnsResponse);
+  return dnsResponse.isAsynchronous();
+}
+
 class UDPTCPCrossQuerySender : public TCPQuerySender
 {
 public:
index 20fe358d1fbebd8dbfa041d6233d5073c6047021..2652a65d629b4dd326b055ad3aec750153dd9749 100644 (file)
@@ -56,6 +56,8 @@ using QTag = std::unordered_map<string, string>;
 
 class IncomingTCPConnectionState;
 
+class TCPQuerySender;
+
 struct ClientState;
 
 struct DNSQuestion
@@ -982,3 +984,4 @@ ssize_t udpClientSendRequestToBackend(const std::shared_ptr<DownstreamState>& ba
 bool sendUDPResponse(int origFD, const PacketBuffer& response, const int delayMsec, const ComboAddress& origDest, const ComboAddress& origRemote);
 void handleResponseSent(const DNSName& qname, const QType& qtype, double udiff, const ComboAddress& client, const ComboAddress& backend, unsigned int size, const dnsheader& cleartextDH, dnsdist::Protocol outgoingProtocol, dnsdist::Protocol incomingProtocol, bool fromBackend);
 void handleResponseSent(const InternalQueryState& ids, double udiff, const ComboAddress& client, const ComboAddress& backend, unsigned int size, const dnsheader& cleartextDH, dnsdist::Protocol outgoingProtocol, bool fromBackend);
+bool handleTimeoutResponseRules(const std::vector<dnsdist::rules::ResponseRuleAction>& rules, InternalQueryState& ids, std::shared_ptr<DownstreamState> ds, std::shared_ptr<TCPQuerySender> sender);
index e1ed355753059cb01d19433832e72fb0f178245f..5efa9019d810a48b0a16fdeafbd524b99d9fa2ca 100644 (file)
@@ -511,6 +511,92 @@ Functions for manipulating Self-Answered Response Rules:
 
   Move the last self answered response rule to the first position.
 
+Timeout
+-------
+
+For Rules related to timeed out queries:
+
+.. function:: addTimeoutResponseAction(DNSRule, action [, options])
+
+  .. versionadded:: 2.0.0
+
+  Add a Rule and Action for timeout responses to the existing rules.
+
+  :param DNSrule rule: A :class:`DNSRule`, e.g. an :func:`AllRule`, or a compounded bunch of rules using e.g. :func:`AndRule`. Before 1.9.0 it was also possible to pass a string (or list of strings) but doing so is now deprecated.
+  :param action: The action to take
+  :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.
+
+.. function:: clearTimeoutResponseRules()
+
+  .. versionadded:: 2.0.0
+
+  Remove all current timeout response rules.
+
+.. function:: getTimeoutResponseRule(selector) -> DNSDistResponseRuleAction
+
+  .. versionadded:: 2.0.0
+
+  Return the timeout 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:: getTopTimeoutResponseRule() -> DNSDistResponseRuleAction
+
+  .. versionadded:: 2.0.0
+
+  Return the current top timeout response rule.
+
+.. function:: mvTimeoutResponseRule(from, to)
+
+  .. versionadded:: 2.0.0
+
+  Move timeout response 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:: mvTimeoutResponseRuleToTop()
+
+  .. versionadded:: 2.0.0
+
+  This function moves the last timeout response rule to the first position.
+
+.. function:: rmTimeoutResponseRule(id)
+
+  .. versionadded:: 2.0.0
+    ``id`` can now be a string representing the name of the rule.
+
+  Remove timeout response rule ``id``.
+
+  :param int id: The position of the rule to remove if ``id`` is numerical, its UUID or name otherwise
+
+.. function:: showTimeoutResponseRules([options])
+
+  .. versionadded:: 2.0.0
+
+  Show all defined timeout response rules, optionally displaying their UUIDs.
+
+  :param table options: A table with key: value pairs with display options.
+
+  Options:
+
+  * ``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:: topTimeoutResponseRules()
+
+  .. versionadded:: 2.0.0
+
+  Show all defined timeout response rules, sorted top-down by match hits.
+
 XFR
 ---
 
index ede608b6c1076717d583337708f31ced9e614676..23c4a39598457238842e7a29ffcfa790f223f2bf 100644 (file)
@@ -56,6 +56,7 @@ GlobalConfiguration
 - **webserver**: :ref:`WebserverConfiguration <yaml-settings-WebserverConfiguration>` - Internal web server configuration
 - **xfr_response_rules**: Sequence of :ref:`ResponseRuleConfiguration <yaml-settings-ResponseRuleConfiguration>` - List of rules executed when a XFR response is received
 - **xsk**: Sequence of :ref:`XskConfiguration <yaml-settings-XskConfiguration>` - List of AF_XDP / XSK objects
+- **timeout_response_rules**: Sequence of :ref:`ResponseRuleConfiguration <yaml-settings-ResponseRuleConfiguration>` - List of rules executed when a timeout event occurred
 
 
 
index 62f564aaacc766758c84900cd053d055cde03f12..98f0b89249e7ffbdb360162e2c53d6fc3293018e 100644 (file)
@@ -273,6 +273,10 @@ struct DOHUnit : public DOHUnitInterface
   [[nodiscard]] const std::string& getHTTPHost() const override;
   [[nodiscard]] const std::string& getHTTPScheme() const override;
   [[nodiscard]] const std::unordered_map<std::string, std::string>& getHTTPHeaders() const override;
+  [[nodiscard]] std::shared_ptr<TCPQuerySender> getQuerySender() const override
+  {
+    return nullptr;
+  }
   void setHTTPResponse(uint16_t statusCode, PacketBuffer&& body, const std::string& contentType="") override;
   void handleTimeout() override;
   void handleUDPResponse(PacketBuffer&& response, InternalQueryState&& state, [[maybe_unused]] const std::shared_ptr<DownstreamState>& downstream) override;
index 63288e26d8d3081c95d5a19472f87883bd358b7b..5c7d0a85847cd73fc30ca7405e15e751376bc5c0 100644 (file)
@@ -65,6 +65,15 @@ bool applyRulesToResponse(const std::vector<dnsdist::rules::ResponseRuleAction>&
   return true;
 }
 
+bool handleTimeoutResponseRules(const std::vector<dnsdist::rules::ResponseRuleAction>& rules, InternalQueryState& ids, std::shared_ptr<DownstreamState> ds, std::shared_ptr<TCPQuerySender> sender)
+{
+  (void)rules;
+  (void)ids;
+  (void)ds;
+  (void)sender;
+  return false;
+}
+
 bool sendUDPResponse(int origFD, const PacketBuffer& response, const int delayMsec, const ComboAddress& origDest, const ComboAddress& origRemote)
 {
   (void)origFD;