]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Use a locked map to store the UDP states when randomizing the IDs
authorRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 10 Feb 2022 14:27:34 +0000 (15:27 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 10 Feb 2022 14:27:34 +0000 (15:27 +0100)
pdns/dnsdist-lua.cc
pdns/dnsdist.cc
pdns/dnsdist.hh
pdns/dnsdistdist/dnsdist-backend.cc
pdns/dnsdistdist/docs/reference/tuning.rst

index 8b7bceb1b35d4a5807395892ff90d73cb765ad55..24cfe202b0cf1309f9f1603328f364aa9d4e4095 100644 (file)
@@ -1279,7 +1279,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
 
   luaCtx.writeFunction("setTCPSendTimeout", [](int timeout) { g_tcpSendTimeout = timeout; });
 
-  luaCtx.writeFunction("setUDPTimeout", [](int timeout) { g_udpTimeout = timeout; });
+  luaCtx.writeFunction("setUDPTimeout", [](int timeout) { DownstreamState::s_udpTimeout = timeout; });
 
   luaCtx.writeFunction("setMaxUDPOutstanding", [](uint64_t max) {
     if (!g_configurationDone) {
index 9695d1619de95487bf04010a315f3459c9628000..d3c6e7d397fc5043048c302f4ad1e74696b329b3 100644 (file)
@@ -137,7 +137,6 @@ GlobalStateHolder<servers_t> g_dstates;
 GlobalStateHolder<NetmaskTree<DynBlock, AddressAndPortRange>> g_dynblockNMG;
 GlobalStateHolder<SuffixMatchTree<DynBlock>> g_dynblockSMT;
 DNSAction::Action g_dynBlockAction = DNSAction::Action::Drop;
-int g_udpTimeout{2};
 
 bool g_servFailOnNoPolicy{false};
 bool g_truncateTC{false};
@@ -597,11 +596,11 @@ void responderThread(std::shared_ptr<DownstreamState> dss)
         dnsheader* dh = reinterpret_cast<struct dnsheader*>(response.data());
         queryId = dh->id;
 
-        if (queryId >= dss->idStates.size()) {
+        IDState* ids = dss->getExistingState(queryId);
+        if (ids == nullptr) {
           continue;
         }
 
-        IDState* ids = &dss->idStates[queryId];
         int64_t usageIndicator = ids->usageIndicator;
 
         if (!IDState::isInUse(usageIndicator)) {
@@ -684,6 +683,7 @@ void responderThread(std::shared_ptr<DownstreamState> dss)
         vinfolog("Got answer from %s, relayed to %s, took %f usec", dss->remote.toStringWithPort(), ids->origRemote.toStringWithPort(), udiff);
 
         handleResponseSent(*ids, udiff, *dr.remote, dss->remote, static_cast<unsigned int>(got), cleartextDH, dss->getProtocol());
+        dss->releaseState(queryId);
 
         dss->latencyUsec = (127.0 * dss->latencyUsec / 128.0) + udiff/128.0;
 
@@ -1846,41 +1846,7 @@ static void healthChecksThread()
       dss->prev.queries.store(dss->queries.load());
       dss->prev.reuseds.store(dss->reuseds.load());
 
-      for (IDState& ids  : dss->idStates) { // timeouts
-        int64_t usageIndicator = ids.usageIndicator;
-        if(IDState::isInUse(usageIndicator) && ids.age++ > g_udpTimeout) {
-          /* We mark the state as unused as soon as possible
-             to limit the risk of racing with the
-             responder thread.
-          */
-          auto oldDU = ids.du;
-
-          if (!ids.tryMarkUnused(usageIndicator)) {
-            /* this state has been altered in the meantime,
-               don't go anywhere near it */
-            continue;
-          }
-          ids.du = nullptr;
-          handleDOHTimeout(DOHUnitUniquePtr(oldDU, DOHUnit::release));
-          oldDU = nullptr;
-          ids.age = 0;
-          dss->reuseds++;
-          --dss->outstanding;
-          ++g_stats.downstreamTimeouts; // this is an 'actively' discovered timeout
-          vinfolog("Had a downstream timeout from %s (%s) for query for %s|%s from %s",
-                   dss->remote.toStringWithPort(), dss->getName(),
-                   ids.qname.toLogString(), QType(ids.qtype).toString(), ids.origRemote.toStringWithPort());
-
-          struct timespec ts;
-          gettime(&ts);
-
-          struct dnsheader fake;
-          memset(&fake, 0, sizeof(fake));
-          fake.id = ids.origID;
-
-          g_rings.insertResponse(ts, ids.origRemote, ids.qname, ids.qtype, std::numeric_limits<unsigned int>::max(), 0, fake, dss->remote, dss->getProtocol());
-        }
-      }
+      dss->handleTimeouts();
     }
 
     handleQueuedHealthChecks(*mplexer);
index 6205895df2b92930942c9e60a6dd5cec29aecfe4..4ee2baf5ebe39f48241277a911b988e0a7ec5a46 100644 (file)
@@ -738,10 +738,11 @@ struct DownstreamState
 private:
   std::string name;
   std::string nameWithAddr;
+  LockGuarded<std::map<uint16_t, IDState>> d_idStatesMap;
+  vector<IDState> idStates;
 public:
   std::shared_ptr<TLSCtx> d_tlsCtx{nullptr};
   std::vector<int> sockets;
-  vector<IDState> idStates;
   set<string> pools;
   std::mutex connectLock;
   std::thread tid;
@@ -882,7 +883,10 @@ public:
   bool passCrossProtocolQuery(std::unique_ptr<CrossProtocolQuery>&& cpq);
   int pickSocketForSending();
   void pickSocketsReadyForReceiving(std::vector<int>& ready);
+  void handleTimeouts();
   IDState* getIDState(unsigned int& id, int64_t& generation);
+  IDState* getExistingState(unsigned int id);
+  void releaseState(unsigned int id);
 
   dnsdist::Protocol getProtocol() const
   {
@@ -898,8 +902,11 @@ public:
     return dnsdist::Protocol::DoUDP;
   }
 
+  static int s_udpTimeout;
   static bool s_randomizeSockets;
   static bool s_randomizeIDs;
+private:
+  void handleTimeout(IDState& ids);
 };
 using servers_t =vector<std::shared_ptr<DownstreamState>>;
 
@@ -998,7 +1005,6 @@ extern bool g_truncateTC;
 extern bool g_fixupCase;
 extern int g_tcpRecvTimeout;
 extern int g_tcpSendTimeout;
-extern int g_udpTimeout;
 extern uint16_t g_maxOutstanding;
 extern std::atomic<bool> g_configurationDone;
 extern boost::optional<uint64_t> g_maxTCPClientThreads;
index e005595aaaf9ef3d193f59eb81557fc2c3791c3b..88631b1d634b7f74f18405f18bc0cdfa1e2ed7b1 100644 (file)
@@ -23,6 +23,7 @@
 #include "dnsdist.hh"
 #include "dnsdist-nghttp2.hh"
 #include "dnsdist-random.hh"
+#include "dnsdist-rings.hh"
 #include "dnsdist-tcp.hh"
 #include "dolog.hh"
 
@@ -175,7 +176,12 @@ DownstreamState::DownstreamState(const ComboAddress& remote_, const ComboAddress
 
 void DownstreamState::connectUDPSockets(size_t numberOfSockets)
 {
-  idStates.resize(g_maxOutstanding);
+  if (s_randomizeIDs) {
+    idStates.clear();
+  }
+  else {
+    idStates.resize(g_maxOutstanding);
+  }
   sockets.resize(numberOfSockets);
 
   if (sockets.size() > 1) {
@@ -245,6 +251,105 @@ void DownstreamState::pickSocketsReadyForReceiving(std::vector<int>& ready)
 
 bool DownstreamState::s_randomizeSockets{false};
 bool DownstreamState::s_randomizeIDs{false};
+int DownstreamState::s_udpTimeout{2};
+
+static bool isIDSExpired(IDState& ids)
+{
+  auto age = ids.age++;
+  return age > DownstreamState::s_udpTimeout;
+}
+
+void DownstreamState::handleTimeout(IDState& ids)
+{
+  /* We mark the state as unused as soon as possible
+     to limit the risk of racing with the
+     responder thread.
+  */
+  auto oldDU = ids.du;
+
+  ids.du = nullptr;
+  handleDOHTimeout(DOHUnitUniquePtr(oldDU, DOHUnit::release));
+  oldDU = nullptr;
+  ids.age = 0;
+  reuseds++;
+  --outstanding;
+  ++g_stats.downstreamTimeouts; // this is an 'actively' discovered timeout
+  vinfolog("Had a downstream timeout from %s (%s) for query for %s|%s from %s",
+           remote.toStringWithPort(), getName(),
+           ids.qname.toLogString(), QType(ids.qtype).toString(), ids.origRemote.toStringWithPort());
+
+  struct timespec ts;
+  gettime(&ts);
+
+  struct dnsheader fake;
+  memset(&fake, 0, sizeof(fake));
+  fake.id = ids.origID;
+
+  g_rings.insertResponse(ts, ids.origRemote, ids.qname, ids.qtype, std::numeric_limits<unsigned int>::max(), 0, fake, remote, getProtocol());
+}
+
+void DownstreamState::handleTimeouts()
+{
+  if (s_randomizeIDs) {
+    auto map = d_idStatesMap.lock();
+    for (auto it = map->begin(); it != map->end(); ) {
+      auto& ids = it->second;
+      if (isIDSExpired(ids)) {
+        handleTimeout(ids);
+        it = map->erase(it);
+        continue;
+      }
+      ++it;
+    }
+  }
+  else {
+    for (IDState& ids : idStates) {
+      int64_t usageIndicator = ids.usageIndicator;
+      if (IDState::isInUse(usageIndicator) && isIDSExpired(ids)) {
+        if (!ids.tryMarkUnused(usageIndicator)) {
+          /* this state has been altered in the meantime,
+             don't go anywhere near it */
+          continue;
+        }
+
+        handleTimeout(ids);
+      }
+    }
+  }
+}
+
+IDState* DownstreamState::getExistingState(unsigned int stateId)
+{
+  if (s_randomizeIDs) {
+    auto map = d_idStatesMap.lock();
+    auto it = map->find(stateId);
+    if (it == map->end()) {
+      return nullptr;
+    }
+    return &it->second;
+  }
+  else {
+    if (stateId >= idStates.size()) {
+      return nullptr;
+    }
+    return &idStates[stateId];
+  }
+}
+
+void DownstreamState::releaseState(unsigned int stateId)
+{
+  if (s_randomizeIDs) {
+    auto map = d_idStatesMap.lock();
+    auto it = map->find(stateId);
+    if (it == map->end()) {
+      return;
+    }
+    if (it->second.isInUse()) {
+      return;
+    }
+    map->erase(it);
+  }
+}
 
 IDState* DownstreamState::getIDState(unsigned int& selectedID, int64_t& generation)
 {
@@ -255,12 +360,21 @@ IDState* DownstreamState::getIDState(unsigned int& selectedID, int64_t& generati
        up to 5 five times. The last selected one is used
        even if it was already in use */
     size_t remainingAttempts = 5;
+    auto map = d_idStatesMap.lock();
+
+    bool done = false;
     do {
-      selectedID = dnsdist::getRandomValue(idStates.size());
-      ids = &idStates[selectedID];
-      remainingAttempts--;
+      selectedID = dnsdist::getRandomValue(std::numeric_limits<uint16_t>::max());
+      auto [it, inserted] = map->insert({selectedID, IDState()});
+      ids = &it->second;
+      if (inserted) {
+        done = true;
+      }
+      else {
+        remainingAttempts--;
+      }
     }
-    while (ids->isInUse() && remainingAttempts > 0);
+    while (!done && remainingAttempts > 0);
   }
   else {
     selectedID = (idOffset++) % idStates.size();
index c8d058fe038d0b9e39c60e466db6d2801ec08b62..9e2f517d22434f966ebc321bb041edb2363da012 100644 (file)
@@ -128,9 +128,8 @@ Tuning related functions
 
   .. versionadded:: 1.8.0
 
-  Setting this parameter to true (default is false) will randomize the IDs in outgoing UDP queries, at a small performance cost. :func:`setMaxUDPOutstanding`
-  should be set at its highest possible value (default since 1.4.0) to make that setting fully efficient. This is only useful if the path between dnsdist
-  and the backend is not trusted and the 'TCP-only', DNS over TLS or DNS over HTTPS transports cannot be used.
+  Setting this parameter to true (default is false) will randomize the IDs in outgoing UDP queries, at a small performance cost, ignoring the :func:`setMaxUDPOutstanding`
+  value. This is only useful if the path between dnsdist and the backend is not trusted and the 'TCP-only', DNS over TLS or DNS over HTTPS transports cannot be used.
   See also :func:`setRandomizedOutgoingSockets`.
 
 .. function:: setRandomizedOutgoingSockets(val):