From baa91396912a0f8d473ef20312a6581a5f5c2586 Mon Sep 17 00:00:00 2001 From: Remi Gacogne Date: Wed, 3 Nov 2021 14:43:00 +0100 Subject: [PATCH] dnsdist: Add a unit test for the outgoing connection cache --- pdns/dnsdistdist/Makefile.am | 1 + pdns/dnsdistdist/dnsdist-tcp-downstream.hh | 55 ++++-- .../test-dnsdist-connections-cache.cc | 186 ++++++++++++++++++ 3 files changed, 221 insertions(+), 21 deletions(-) create mode 100644 pdns/dnsdistdist/test-dnsdist-connections-cache.cc diff --git a/pdns/dnsdistdist/Makefile.am b/pdns/dnsdistdist/Makefile.am index 76260871af..f1a614026b 100644 --- a/pdns/dnsdistdist/Makefile.am +++ b/pdns/dnsdistdist/Makefile.am @@ -285,6 +285,7 @@ testrunner_SOURCES = \ test-credentials_cc.cc \ test-delaypipe_hh.cc \ test-dnscrypt_cc.cc \ + test-dnsdist-connections-cache.cc \ test-dnsdist_cc.cc \ test-dnsdistdynblocks_hh.cc \ test-dnsdistkvs_cc.cc \ diff --git a/pdns/dnsdistdist/dnsdist-tcp-downstream.hh b/pdns/dnsdistdist/dnsdist-tcp-downstream.hh index 0eb6c1a225..ace31e4e48 100644 --- a/pdns/dnsdistdist/dnsdist-tcp-downstream.hh +++ b/pdns/dnsdistdist/dnsdist-tcp-downstream.hh @@ -316,26 +316,6 @@ public: s_maxIdleTime = max; } - bool isConnectionUsable(const std::shared_ptr& conn, const struct timeval& now, const struct timeval& freshCutOff) - { - if (!conn->canBeReused()) { - return false; - } - - /* for connections that have not been used very recently, - check whether they have been closed in the meantime */ - if (freshCutOff < conn->getLastDataReceivedTime()) { - /* used recently enough, skip the check */ - return true; - } - - if (conn->isUsable()) { - return true; - } - - return false; - } - std::shared_ptr getConnectionToDownstream(std::unique_ptr& mplexer, const std::shared_ptr& ds, const struct timeval& now, std::string&& proxyProtocolPayload) { struct timeval freshCutOff = now; @@ -375,7 +355,11 @@ public: auto newConnection = std::make_shared(ds, mplexer, now, std::move(proxyProtocolPayload)); if (!haveProxyProtocol) { - d_downstreamConnections[backendId].push_front(newConnection); + auto& list = d_downstreamConnections[backendId]; + if (list.size() == s_maxCachedConnectionsPerDownstream) { + list.pop_back(); + } + list.push_front(newConnection); } return newConnection; @@ -446,6 +430,15 @@ public: return count; } + size_t count() const + { + size_t count = 0; + for (const auto& downstream : d_downstreamConnections) { + count += downstream.second.size(); + } + return count; + } + bool removeDownstreamConnection(std::shared_ptr& conn) { bool found = false; @@ -467,6 +460,26 @@ public: protected: + bool isConnectionUsable(const std::shared_ptr& conn, const struct timeval& now, const struct timeval& freshCutOff) + { + if (!conn->canBeReused()) { + return false; + } + + /* for connections that have not been used very recently, + check whether they have been closed in the meantime */ + if (freshCutOff < conn->getLastDataReceivedTime()) { + /* used recently enough, skip the check */ + return true; + } + + if (conn->isUsable()) { + return true; + } + + return false; + } + static size_t s_maxCachedConnectionsPerDownstream; static uint16_t s_cleanupInterval; static uint16_t s_maxIdleTime; diff --git a/pdns/dnsdistdist/test-dnsdist-connections-cache.cc b/pdns/dnsdistdist/test-dnsdist-connections-cache.cc new file mode 100644 index 0000000000..6f708deb6e --- /dev/null +++ b/pdns/dnsdistdist/test-dnsdist-connections-cache.cc @@ -0,0 +1,186 @@ +/* + * This file is part of PowerDNS or dnsdist. + * Copyright -- PowerDNS.COM B.V. and its contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * In addition, for the avoidance of any doubt, permission is granted to + * link this program with OpenSSL and to (re)distribute the binaries + * produced as the result of such linking. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_NO_MAIN + +#include + +#include "dnsdist-tcp-downstream.hh" + +class MockupConnection +{ +public: + MockupConnection(const std::shared_ptr&, std::unique_ptr&, const struct timeval&, std::string&&) + { + } + + bool canBeReused() const + { + return d_reusable; + } + + bool isUsable() const + { + return d_usable; + } + + bool willBeReusable(bool) const + { + return d_reusable; + } + + void setReused() + { + } + + struct timeval getLastDataReceivedTime() const + { + return d_lastDataReceivedTime; + } + + bool isIdle() const + { + return d_idle; + } + + void stopIO() + { + } + + struct timeval d_lastDataReceivedTime{0, 0}; + bool d_reusable{true}; + bool d_usable{true}; + bool d_idle{false}; +}; + +BOOST_AUTO_TEST_SUITE(test_dnsdist_connections_cache) + +BOOST_AUTO_TEST_CASE(test_ConnectionsCache) +{ + DownstreamConnectionsManager manager; + const size_t maxConnPerDownstream = 5; + const uint16_t cleanupInterval = 1; + const uint16_t maxIdleTime = 5; + manager.setMaxCachedConnectionsPerDownstream(maxConnPerDownstream); + manager.setCleanupInterval(cleanupInterval); + manager.setMaxIdleTime(maxIdleTime); + + auto mplexer = std::unique_ptr(FDMultiplexer::getMultiplexerSilent()); + auto downstream1 = std::make_shared(ComboAddress("192.0.2.1")); + auto downstream2 = std::make_shared(ComboAddress("192.0.2.2")); + struct timeval now; + gettimeofday(&now, nullptr); + + auto conn = manager.getConnectionToDownstream(mplexer, downstream1, now, std::string()); + BOOST_REQUIRE(conn != nullptr); + BOOST_CHECK_EQUAL(manager.count(), 1U); + + /* since the connection can be reused, we should get the same one */ + { + auto conn1 = manager.getConnectionToDownstream(mplexer, downstream1, now, std::string()); + BOOST_CHECK(conn.get() == conn1.get()); + BOOST_CHECK_EQUAL(manager.count(), 1U); + } + + /* if we mark it non-usable, we should get a new one */ + conn->d_usable = false; + auto conn2 = manager.getConnectionToDownstream(mplexer, downstream1, now, std::string()); + BOOST_CHECK(conn.get() != conn2.get()); + BOOST_CHECK_EQUAL(manager.count(), 2U); + + /* since the second connection can be reused, we should get it */ + { + auto conn3 = manager.getConnectionToDownstream(mplexer, downstream1, now, std::string()); + BOOST_CHECK(conn3.get() == conn2.get()); + BOOST_CHECK_EQUAL(manager.count(), 2U); + } + + /* different downstream so different connection */ + auto differentConn = manager.getConnectionToDownstream(mplexer, downstream2, now, std::string()); + BOOST_REQUIRE(differentConn != nullptr); + BOOST_CHECK(differentConn.get() != conn.get()); + BOOST_CHECK(differentConn.get() != conn2.get()); + BOOST_CHECK_EQUAL(manager.count(), 3U); + { + /* but we should be able to reuse it */ + auto sameConn = manager.getConnectionToDownstream(mplexer, downstream2, now, std::string()); + BOOST_CHECK(sameConn.get() == differentConn.get()); + BOOST_CHECK_EQUAL(manager.count(), 3U); + } + + struct timeval later = now; + later.tv_sec += cleanupInterval + 1; + + /* mark the second connection as no longer usable */ + conn2->d_usable = false; + /* first one as well but still fresh so it will not get checked */ + conn->d_usable = true; + conn->d_lastDataReceivedTime = later; + /* third one is usable but idle for too long */ + differentConn->d_idle = true; + differentConn->d_lastDataReceivedTime = later; + differentConn->d_lastDataReceivedTime.tv_sec -= (maxIdleTime + 1); + + /* we should not do an actual cleanup attempt since the last cleanup was done recently */ + manager.cleanupClosedConnections(now); + BOOST_CHECK_EQUAL(manager.count(), 3U); + + manager.cleanupClosedConnections(later); + BOOST_CHECK_EQUAL(manager.count(), 1U); + + /* mark the remaining conn as non-usable, to get new ones */ + conn->d_usable = false; + conn->d_lastDataReceivedTime.tv_sec = 0; + + std::vector> conns = { conn }; + while (conns.size() < maxConnPerDownstream) { + auto newConn = manager.getConnectionToDownstream(mplexer, downstream1, now, std::string()); + newConn->d_usable = false; + conns.push_back(newConn); + BOOST_CHECK_EQUAL(manager.count(), conns.size()); + } + + /* if we add a new one, the oldest should get expunged */ + auto newConn = manager.getConnectionToDownstream(mplexer, downstream1, now, std::string()); + BOOST_CHECK_EQUAL(manager.count(), maxConnPerDownstream); + + { + /* mark all connections as not usable anymore */ + for (auto& c : conns) { + c->d_usable = false; + } + + /* except the last one */ + newConn->d_usable = true; + + BOOST_CHECK_EQUAL(manager.count(), maxConnPerDownstream); + later.tv_sec += cleanupInterval + 1; + manager.cleanupClosedConnections(later); + BOOST_CHECK_EQUAL(manager.count(), 1U); + } + + conns.clear(); + auto cleared = manager.clear(); + BOOST_CHECK_EQUAL(cleared, 1U); +} + +BOOST_AUTO_TEST_SUITE_END(); -- 2.47.2