From: Charles-Henri Bruyand Date: Tue, 21 Jun 2022 07:50:52 +0000 (+0200) Subject: dnsdist: fill ringbuffers with responses served from the cache X-Git-Tag: auth-4.8.0-alpha0~43^2~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=12593efb74458415867f0c104f92ca11446ced24;p=thirdparty%2Fpdns.git dnsdist: fill ringbuffers with responses served from the cache --- diff --git a/pdns/dnsdist-lua-inspection.cc b/pdns/dnsdist-lua-inspection.cc index 0e39d93210..c95f23ca10 100644 --- a/pdns/dnsdist-lua-inspection.cc +++ b/pdns/dnsdist-lua-inspection.cc @@ -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::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) { diff --git a/pdns/dnsdist-tcp.cc b/pdns/dnsdist-tcp.cc index 11de6df453..d7b444c22a 100644 --- a/pdns/dnsdist-tcp.cc +++ b/pdns/dnsdist-tcp.cc @@ -257,6 +257,10 @@ static void handleResponseSent(std::shared_ptr& stat backendProtocol = dnsdist::Protocol::DoTCP; } ::handleResponseSent(ids, udiff, state->d_ci.remote, ds->d_config.remote, static_cast(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(currentResponse.d_buffer.size()), currentResponse.d_cleartextDH, ids.protocol); } } @@ -743,6 +747,13 @@ static void handleQuery(std::shared_ptr& 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)); diff --git a/pdns/dnsdist.cc b/pdns/dnsdist.cc index 9c2333a168..971f9c1b41 100644 --- a/pdns/dnsdist.cc +++ b/pdns/dnsdist.cc @@ -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(udiff), size, cleartextDH, backend, protocol); + g_rings.insertResponse(ts, client, qname, qtype, static_cast(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; } diff --git a/pdns/dnsdist.hh b/pdns/dnsdist.hh index cad4862369..db8454853a 100644 --- a/pdns/dnsdist.hh +++ b/pdns/dnsdist.hh @@ -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& 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); diff --git a/pdns/dnsdistdist/doh.cc b/pdns/dnsdistdist/doh.cc index 8975dab5ad..d0d09211a6 100644 --- a/pdns/dnsdistdist/doh.cc +++ b/pdns/dnsdistdist/doh.cc @@ -620,6 +620,9 @@ static void processDOHQuery(DOHUnitUniquePtr&& du) if (du->response.empty()) { du->response = std::move(du->query); } + auto dh = const_cast(reinterpret_cast(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; } diff --git a/regression-tests.dnsdist/test_DynBlocks.py b/regression-tests.dnsdist/test_DynBlocks.py index 788a0b17d7..949080e879 100644 --- a/regression-tests.dnsdist/test_DynBlocks.py +++ b/regression-tests.dnsdist/test_DynBlocks.py @@ -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