]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: fill ringbuffers with responses served from the cache
authorCharles-Henri Bruyand <charles-henri.bruyand@open-xchange.com>
Tue, 21 Jun 2022 07:50:52 +0000 (09:50 +0200)
committerCharles-Henri Bruyand <charles-henri.bruyand@open-xchange.com>
Wed, 22 Jun 2022 14:04:17 +0000 (16:04 +0200)
pdns/dnsdist-lua-inspection.cc
pdns/dnsdist-tcp.cc
pdns/dnsdist.cc
pdns/dnsdist.hh
pdns/dnsdistdist/doh.cc
regression-tests.dnsdist/test_DynBlocks.py

index 0e39d93210b22519e0d3c0b507a6d4338f3f487d..c95f23ca10d302507d5bc65d22887d71d6c332ad 100644 (file)
@@ -527,11 +527,17 @@ void setupLuaInspection(LuaContext& luaCtx)
            extra.clear();
           }
 
+          std::string server = c.ds.toStringWithPort();
+          std::string protocol = dnsdist::Protocol(c.protocol).toString();
+          if (server == "0.0.0.0:0") {
+            server = "Cache";
+            protocol = "-";
+          }
           if (c.usec != std::numeric_limits<decltype(c.usec)>::max()) {
-            out.emplace(c.when, (fmt % DiffTime(now, c.when) % c.requestor.toStringWithPort() % dnsdist::Protocol(c.protocol).toString() % c.ds.toStringWithPort() % htons(c.dh.id) % c.name.toString() % qt.toString() % (c.usec / 1000.0) % (c.dh.tc ? "TC" : "") % (c.dh.rd ? "RD" : "") % (c.dh.aa ? "AA" : "") % (RCode::to_s(c.dh.rcode) + extra)).str());
+            out.emplace(c.when, (fmt % DiffTime(now, c.when) % c.requestor.toStringWithPort() % protocol % server % htons(c.dh.id) % c.name.toString() % qt.toString() % (c.usec / 1000.0) % (c.dh.tc ? "TC" : "") % (c.dh.rd ? "RD" : "") % (c.dh.aa ? "AA" : "") % (RCode::to_s(c.dh.rcode) + extra)).str());
           }
           else {
-            out.emplace(c.when, (fmt % DiffTime(now, c.when) % c.requestor.toStringWithPort() % dnsdist::Protocol(c.protocol).toString() % c.ds.toStringWithPort() % htons(c.dh.id) % c.name.toString() % qt.toString() % "T.O" % (c.dh.tc ? "TC" : "") % (c.dh.rd ? "RD" : "") % (c.dh.aa ? "AA" : "") % (RCode::to_s(c.dh.rcode) + extra)).str());
+            out.emplace(c.when, (fmt % DiffTime(now, c.when) % c.requestor.toStringWithPort() % protocol % server % htons(c.dh.id) % c.name.toString() % qt.toString() % "T.O" % (c.dh.tc ? "TC" : "") % (c.dh.rd ? "RD" : "") % (c.dh.aa ? "AA" : "") % (RCode::to_s(c.dh.rcode) + extra)).str());
           }
 
           if (limit && *limit == ++num) {
index 11de6df4538ca4f524a1b7002b15dbcab189d8a1..d7b444c22a075dad4091358d7e57d778bd347930 100644 (file)
@@ -257,6 +257,10 @@ static void handleResponseSent(std::shared_ptr<IncomingTCPConnectionState>& stat
       backendProtocol = dnsdist::Protocol::DoTCP;
     }
     ::handleResponseSent(ids, udiff, state->d_ci.remote, ds->d_config.remote, static_cast<unsigned int>(currentResponse.d_buffer.size()), currentResponse.d_cleartextDH, backendProtocol);
+  } else {
+    const auto& ids = currentResponse.d_idstate;
+    double udiff = ids.sentTime.udiff();
+    ::handleResponseSent(ids, udiff, state->d_ci.remote, ComboAddress(), static_cast<unsigned int>(currentResponse.d_buffer.size()), currentResponse.d_cleartextDH, ids.protocol);
   }
 }
 
@@ -743,6 +747,13 @@ static void handleQuery(std::shared_ptr<IncomingTCPConnectionState>& state, cons
     TCPResponse response;
     response.d_selfGenerated = true;
     response.d_buffer = std::move(state->d_buffer);
+    setIDStateFromDNSQuestion(response.d_idstate, dq, std::move(qname));
+    response.d_idstate.origID = dh->id;
+    response.d_idstate.cs = state->d_ci.cs;
+
+    DNSResponse dr = makeDNSResponseFromIDState(response.d_idstate, response.d_buffer);
+    memcpy(&response.d_cleartextDH, dr.getHeader(), sizeof(response.d_cleartextDH));
+
     state->d_state = IncomingTCPConnectionState::State::idle;
     ++state->d_currentQueriesCount;
     state->queueResponse(state, now, std::move(response));
index 9c2333a168e3575d0088ba947788fdd8513fb0d7..971f9c1b4146cadb46215627f29513d60e6a171d 100644 (file)
@@ -558,10 +558,15 @@ static bool sendUDPResponse(int origFD, const PacketBuffer& response, const int
 }
 
 void handleResponseSent(const IDState& ids, double udiff, const ComboAddress& client, const ComboAddress& backend, unsigned int size, const dnsheader& cleartextDH, dnsdist::Protocol protocol)
+{
+  handleResponseSent(ids.qname, ids.qtype, udiff, client, backend, size, cleartextDH, protocol);
+}
+
+void handleResponseSent(const DNSName& qname, const QType& qtype, double udiff, const ComboAddress& client, const ComboAddress& backend, unsigned int size, const dnsheader& cleartextDH, dnsdist::Protocol protocol)
 {
   struct timespec ts;
   gettime(&ts);
-  g_rings.insertResponse(ts, client, ids.qname, ids.qtype, static_cast<unsigned int>(udiff), size, cleartextDH, backend, protocol);
+  g_rings.insertResponse(ts, client, qname, qtype, static_cast<unsigned int>(udiff), size, cleartextDH, backend, protocol);
 
   switch (cleartextDH.rcode) {
   case RCode::NXDomain:
@@ -1530,6 +1535,8 @@ static void processUDPQuery(ClientState& cs, LocalHolders& holders, const struct
 #endif /* DISABLE_RECVMMSG */
       /* we use dest, always, because we don't want to use the listening address to send a response since it could be 0.0.0.0 */
       sendUDPResponse(cs.udpFD, query, dq.delayMsec, dest, remote);
+
+      handleResponseSent(qname, qtype, 0., remote, ComboAddress(), query.size(), *dh, dnsdist::Protocol::DoUDP);
       return;
     }
 
index cad4862369d72fe84dfefadfde5e887914293444..db8454853a1642e85b4c2045ae66dbaa1ae1f05f 100644 (file)
@@ -1128,5 +1128,6 @@ DNSResponse makeDNSResponseFromIDState(IDState& ids, PacketBuffer& data);
 void setIDStateFromDNSQuestion(IDState& ids, DNSQuestion& dq, DNSName&& qname);
 
 ssize_t udpClientSendRequestToBackend(const std::shared_ptr<DownstreamState>& ss, const int sd, const PacketBuffer& request, bool healthCheck = false);
+void handleResponseSent(const DNSName& qname, const QType& qtype, double udiff, const ComboAddress& client, const ComboAddress& backend, unsigned int size, const dnsheader& cleartextDH, dnsdist::Protocol protocol);
 void handleResponseSent(const IDState& ids, double udiff, const ComboAddress& client, const ComboAddress& backend, unsigned int size, const dnsheader& cleartextDH, dnsdist::Protocol protocol);
 
index 8975dab5ad54be8c2bbc5299c9006bf085c5b490..d0d09211a6ba81501d42640b8843e9a803ec1cb6 100644 (file)
@@ -620,6 +620,9 @@ static void processDOHQuery(DOHUnitUniquePtr&& du)
       if (du->response.empty()) {
         du->response = std::move(du->query);
       }
+      auto dh = const_cast<struct dnsheader*>(reinterpret_cast<const struct dnsheader*>(du->response.data()));
+
+      handleResponseSent(qname, QType(qtype), 0., du->downstream->d_config.remote, ComboAddress(), du->response.size(), *dh, du->downstream->getProtocol());
       sendDoHUnitToTheMainThread(std::move(du), "DoH self-answered response");
       return;
     }
index 788a0b17d7341b4fc3ca93f1b2c5af8eb69b2e51..949080e879100033d85e6c021be244c998c53204 100644 (file)
@@ -827,6 +827,83 @@ class TestDynBlockServFails(DynBlocksTest):
         name = 'servfailrate.dynblocks.tests.powerdns.com.'
         self.doTestRCodeRate(name, dns.rcode.SERVFAIL)
 
+class TestDynBlockServFailsCached(DynBlocksTest):
+
+    _dynBlockQPS = 10
+    _dynBlockPeriod = 2
+    _dynBlockDuration = 5
+    _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
+    _config_template = """
+    pc = newPacketCache(10000, {maxTTL=86400, minTTL=0, temporaryFailureTTL=60, staleTTL=60, dontAge=false})
+    getPool(""):setCache(pc)
+    function maintenance()
+           addDynBlocks(exceedServFails(%d, %d), "Exceeded servfail rate", %d)
+    end
+    newServer{address="127.0.0.1:%s"}
+    """
+
+    def testDynBlocksServFailRateCached(self):
+        """
+        Dyn Blocks: Make sure cache hit responses also gets inserted into rings
+        """
+        name = 'servfailrate.dynblocks.tests.powerdns.com.'
+        rcode = dns.rcode.SERVFAIL
+        query = dns.message.make_query(name, 'A', 'IN')
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '192.0.2.1')
+        response.answer.append(rrset)
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.set_rcode(rcode)
+
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            print(method, "()")
+            sender = getattr(self, method)
+
+            # fill the cache
+            (receivedQuery, receivedResponse) = sender(query, expectedResponse)
+            receivedQuery.id = query.id
+            self.assertEqual(query, receivedQuery)
+            self.assertEqual(expectedResponse, receivedResponse)
+
+            # wait for the maintenance function to run
+            time.sleep(2)
+
+            # we should NOT be dropped!
+            (_, receivedResponse) = sender(query, response=None)
+            self.assertEqual(receivedResponse, expectedResponse)
+
+            # now with rcode!
+            sent = 0
+            allowed = 0
+            for _ in range((self._dynBlockQPS * self._dynBlockPeriod) + 1):
+                (_, receivedResponse) = sender(query, expectedResponse)
+                sent = sent + 1
+                self.assertEqual(expectedResponse, receivedResponse)
+                allowed = allowed + 1
+            # we might be already blocked, but we should have been able to send
+            # at least self._dynBlockQPS queries
+            self.assertGreaterEqual(allowed, self._dynBlockQPS)
+
+            if allowed == sent:
+                # wait for the maintenance function to run
+                time.sleep(2)
+
+            # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.assertEqual(receivedResponse, None)
+
+            # wait until we are not blocked anymore
+            time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
+
+            # this one should succeed
+            (receivedQuery, receivedResponse) = sender(query, response=None)
+            self.assertEqual(expectedResponse, receivedResponse)
+
 class TestDynBlockAllowlist(DynBlocksTest):
 
     _dynBlockQPS = 10