mirror of
https://github.com/XRPLF/clio.git
synced 2025-12-06 17:27:58 +00:00
@@ -10,7 +10,7 @@ if(NOT googletest_POPULATED)
|
|||||||
add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR} EXCLUDE_FROM_ALL)
|
add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR} EXCLUDE_FROM_ALL)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_link_libraries(clio_tests PUBLIC clio gtest_main)
|
target_link_libraries(clio_tests PUBLIC clio gmock_main)
|
||||||
target_include_directories(clio_tests PRIVATE unittests)
|
target_include_directories(clio_tests PRIVATE unittests)
|
||||||
|
|
||||||
enable_testing()
|
enable_testing()
|
||||||
|
|||||||
@@ -113,7 +113,8 @@ if(BUILD_TESTS)
|
|||||||
unittests/RPCErrors.cpp
|
unittests/RPCErrors.cpp
|
||||||
unittests/Backend.cpp
|
unittests/Backend.cpp
|
||||||
unittests/Logger.cpp
|
unittests/Logger.cpp
|
||||||
unittests/Config.cpp)
|
unittests/Config.cpp
|
||||||
|
unittests/DOSGuard.cpp)
|
||||||
include(CMake/deps/gtest.cmake)
|
include(CMake/deps/gtest.cmake)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|||||||
@@ -362,7 +362,7 @@ private:
|
|||||||
}
|
}
|
||||||
else if constexpr (std::is_same_v<Return, double>)
|
else if constexpr (std::is_same_v<Return, double>)
|
||||||
{
|
{
|
||||||
if (not value.is_double())
|
if (not value.is_number())
|
||||||
has_error = true;
|
has_error = true;
|
||||||
}
|
}
|
||||||
else if constexpr (
|
else if constexpr (
|
||||||
|
|||||||
@@ -201,20 +201,19 @@ try
|
|||||||
boost::asio::io_context ioc{threads};
|
boost::asio::io_context ioc{threads};
|
||||||
|
|
||||||
// Rate limiter, to prevent abuse
|
// Rate limiter, to prevent abuse
|
||||||
DOSGuard dosGuard{config, ioc};
|
auto sweepHandler = IntervalSweepHandler{config, ioc};
|
||||||
|
auto dosGuard = DOSGuard{config, sweepHandler};
|
||||||
|
|
||||||
// Interface to the database
|
// Interface to the database
|
||||||
std::shared_ptr<BackendInterface> backend{
|
auto backend = Backend::make_Backend(ioc, config);
|
||||||
Backend::make_Backend(ioc, config)};
|
|
||||||
|
|
||||||
// Manages clients subscribed to streams
|
// Manages clients subscribed to streams
|
||||||
std::shared_ptr<SubscriptionManager> subscriptions{
|
auto subscriptions =
|
||||||
SubscriptionManager::make_SubscriptionManager(config, backend)};
|
SubscriptionManager::make_SubscriptionManager(config, backend);
|
||||||
|
|
||||||
// Tracks which ledgers have been validated by the
|
// Tracks which ledgers have been validated by the
|
||||||
// network
|
// network
|
||||||
std::shared_ptr<NetworkValidatedLedgers> ledgers{
|
auto ledgers = NetworkValidatedLedgers::make_ValidatedLedgers();
|
||||||
NetworkValidatedLedgers::make_ValidatedLedgers()};
|
|
||||||
|
|
||||||
// Handles the connection to one or more rippled nodes.
|
// Handles the connection to one or more rippled nodes.
|
||||||
// ETL uses the balancer to extract data.
|
// ETL uses the balancer to extract data.
|
||||||
|
|||||||
@@ -20,57 +20,80 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <boost/asio.hpp>
|
#include <boost/asio.hpp>
|
||||||
|
#include <boost/iterator/transform_iterator.hpp>
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
|
||||||
#include <config/Config.h>
|
#include <config/Config.h>
|
||||||
|
|
||||||
class DOSGuard
|
namespace clio {
|
||||||
|
|
||||||
|
class BaseDOSGuard
|
||||||
{
|
{
|
||||||
boost::asio::io_context& ctx_;
|
public:
|
||||||
std::mutex mtx_; // protects ipFetchCount_
|
virtual ~BaseDOSGuard() = default;
|
||||||
|
|
||||||
|
virtual void
|
||||||
|
clear() noexcept = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A simple denial of service guard used for rate limiting.
|
||||||
|
*
|
||||||
|
* @tparam SweepHandler Type of the sweep handler
|
||||||
|
*/
|
||||||
|
template <typename SweepHandler>
|
||||||
|
class BasicDOSGuard : public BaseDOSGuard
|
||||||
|
{
|
||||||
|
mutable std::mutex mtx_; // protects ipFetchCount_
|
||||||
std::unordered_map<std::string, std::uint32_t> ipFetchCount_;
|
std::unordered_map<std::string, std::uint32_t> ipFetchCount_;
|
||||||
std::unordered_map<std::string, std::uint32_t> ipConnCount_;
|
std::unordered_map<std::string, std::uint32_t> ipConnCount_;
|
||||||
std::unordered_set<std::string> const whitelist_;
|
std::unordered_set<std::string> const whitelist_;
|
||||||
|
|
||||||
std::uint32_t const maxFetches_;
|
std::uint32_t const maxFetches_;
|
||||||
std::uint32_t const sweepInterval_;
|
|
||||||
std::uint32_t const maxConnCount_;
|
std::uint32_t const maxConnCount_;
|
||||||
clio::Logger log_{"RPC"};
|
clio::Logger log_{"RPC"};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
DOSGuard(clio::Config const& config, boost::asio::io_context& ctx)
|
/**
|
||||||
: ctx_{ctx}
|
* @brief Constructs a new DOS guard.
|
||||||
, whitelist_{getWhitelist(config)}
|
*
|
||||||
|
* @param config Clio config
|
||||||
|
* @param sweepHandler Sweep handler that implements the sweeping behaviour
|
||||||
|
*/
|
||||||
|
BasicDOSGuard(clio::Config const& config, SweepHandler& sweepHandler)
|
||||||
|
: whitelist_{getWhitelist(config)}
|
||||||
, maxFetches_{config.valueOr("dos_guard.max_fetches", 100000000u)}
|
, maxFetches_{config.valueOr("dos_guard.max_fetches", 100000000u)}
|
||||||
, sweepInterval_{config.valueOr("dos_guard.sweep_interval", 10u)}
|
|
||||||
, maxConnCount_{config.valueOr("dos_guard.max_connections", 1u)}
|
, maxConnCount_{config.valueOr("dos_guard.max_connections", 1u)}
|
||||||
{
|
{
|
||||||
createTimer();
|
sweepHandler.setup(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
/**
|
||||||
createTimer()
|
* @brief Check whether an ip address is in the whitelist or not
|
||||||
{
|
*
|
||||||
auto wait = std::chrono::seconds(sweepInterval_);
|
* @param ip The ip address to check
|
||||||
std::shared_ptr<boost::asio::steady_timer> timer =
|
* @return true
|
||||||
std::make_shared<boost::asio::steady_timer>(
|
* @return false
|
||||||
ctx_, std::chrono::steady_clock::now() + wait);
|
*/
|
||||||
timer->async_wait(
|
[[nodiscard]] bool
|
||||||
[timer, this](const boost::system::error_code& error) {
|
isWhiteListed(std::string const& ip) const noexcept
|
||||||
clear();
|
|
||||||
createTimer();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
isWhiteListed(std::string const& ip)
|
|
||||||
{
|
{
|
||||||
return whitelist_.contains(ip);
|
return whitelist_.contains(ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
/**
|
||||||
isOk(std::string const& ip)
|
* @brief Check whether an ip address is currently rate limited or not
|
||||||
|
*
|
||||||
|
* @param ip The ip address to check
|
||||||
|
* @return true If not rate limited
|
||||||
|
* @return false If rate limited and the request should not be processed
|
||||||
|
*/
|
||||||
|
[[nodiscard]] bool
|
||||||
|
isOk(std::string const& ip) const noexcept
|
||||||
{
|
{
|
||||||
if (whitelist_.contains(ip))
|
if (whitelist_.contains(ip))
|
||||||
return true;
|
return true;
|
||||||
@@ -97,8 +120,13 @@ public:
|
|||||||
return fetchesOk && connsOk;
|
return fetchesOk && connsOk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Increment connection count for the given ip address
|
||||||
|
*
|
||||||
|
* @param ip
|
||||||
|
*/
|
||||||
void
|
void
|
||||||
increment(std::string const& ip)
|
increment(std::string const& ip) noexcept
|
||||||
{
|
{
|
||||||
if (whitelist_.contains(ip))
|
if (whitelist_.contains(ip))
|
||||||
return;
|
return;
|
||||||
@@ -106,8 +134,13 @@ public:
|
|||||||
ipConnCount_[ip]++;
|
ipConnCount_[ip]++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Decrement connection count for the given ip address
|
||||||
|
*
|
||||||
|
* @param ip
|
||||||
|
*/
|
||||||
void
|
void
|
||||||
decrement(std::string const& ip)
|
decrement(std::string const& ip) noexcept
|
||||||
{
|
{
|
||||||
if (whitelist_.contains(ip))
|
if (whitelist_.contains(ip))
|
||||||
return;
|
return;
|
||||||
@@ -118,8 +151,20 @@ public:
|
|||||||
ipConnCount_.erase(ip);
|
ipConnCount_.erase(ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
/**
|
||||||
add(std::string const& ip, uint32_t numObjects)
|
* @brief Adds numObjects of usage for the given ip address.
|
||||||
|
*
|
||||||
|
* If the total sums up to a value equal or larger than maxFetches_
|
||||||
|
* the operation is no longer allowed and false is returned; true is
|
||||||
|
* returned otherwise.
|
||||||
|
*
|
||||||
|
* @param ip
|
||||||
|
* @param numObjects
|
||||||
|
* @return true
|
||||||
|
* @return false
|
||||||
|
*/
|
||||||
|
[[maybe_unused]] bool
|
||||||
|
add(std::string const& ip, uint32_t numObjects) noexcept
|
||||||
{
|
{
|
||||||
if (whitelist_.contains(ip))
|
if (whitelist_.contains(ip))
|
||||||
return true;
|
return true;
|
||||||
@@ -136,15 +181,19 @@ public:
|
|||||||
return isOk(ip);
|
return isOk(ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Instantly clears all fetch counters added by @see add(std::string
|
||||||
|
* const&, uint32_t)
|
||||||
|
*/
|
||||||
void
|
void
|
||||||
clear()
|
clear() noexcept override
|
||||||
{
|
{
|
||||||
std::unique_lock lck(mtx_);
|
std::unique_lock lck(mtx_);
|
||||||
ipFetchCount_.clear();
|
ipFetchCount_.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unordered_set<std::string> const
|
[[nodiscard]] std::unordered_set<std::string> const
|
||||||
getWhitelist(clio::Config const& config) const
|
getWhitelist(clio::Config const& config) const
|
||||||
{
|
{
|
||||||
using T = std::unordered_set<std::string> const;
|
using T = std::unordered_set<std::string> const;
|
||||||
@@ -157,3 +206,72 @@ private:
|
|||||||
boost::transform_iterator(std::end(whitelist), transform)};
|
boost::transform_iterator(std::end(whitelist), transform)};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sweep handler using a steady_timer and boost::asio::io_context.
|
||||||
|
*/
|
||||||
|
class IntervalSweepHandler
|
||||||
|
{
|
||||||
|
std::chrono::milliseconds sweepInterval_;
|
||||||
|
std::reference_wrapper<boost::asio::io_context> ctx_;
|
||||||
|
BaseDOSGuard* dosGuard_ = nullptr;
|
||||||
|
|
||||||
|
boost::asio::steady_timer timer_{ctx_.get()};
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Construct a new interval-based sweep handler
|
||||||
|
*
|
||||||
|
* @param config Clio config
|
||||||
|
* @param ctx The boost::asio::io_context
|
||||||
|
*/
|
||||||
|
IntervalSweepHandler(
|
||||||
|
clio::Config const& config,
|
||||||
|
boost::asio::io_context& ctx)
|
||||||
|
: sweepInterval_{std::max(
|
||||||
|
1u,
|
||||||
|
static_cast<uint32_t>(
|
||||||
|
config.valueOr("dos_guard.sweep_interval", 10.0) * 1000.0))}
|
||||||
|
, ctx_{std::ref(ctx)}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
~IntervalSweepHandler()
|
||||||
|
{
|
||||||
|
timer_.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief This setup member function is called by @ref BasicDOSGuard during
|
||||||
|
* its initialization.
|
||||||
|
*
|
||||||
|
* @param guard Pointer to the dos guard
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
setup(BaseDOSGuard* guard)
|
||||||
|
{
|
||||||
|
assert(dosGuard_ == nullptr);
|
||||||
|
dosGuard_ = guard;
|
||||||
|
assert(dosGuard_ != nullptr);
|
||||||
|
|
||||||
|
createTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void
|
||||||
|
createTimer()
|
||||||
|
{
|
||||||
|
timer_.expires_after(sweepInterval_);
|
||||||
|
timer_.async_wait([this](boost::system::error_code const& error) {
|
||||||
|
if (error == boost::asio::error::operation_aborted)
|
||||||
|
return;
|
||||||
|
|
||||||
|
dosGuard_->clear();
|
||||||
|
createTimer();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using DOSGuard = BasicDOSGuard<IntervalSweepHandler>;
|
||||||
|
|
||||||
|
} // namespace clio
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ class HttpBase : public util::Taggable
|
|||||||
std::shared_ptr<ETLLoadBalancer> balancer_;
|
std::shared_ptr<ETLLoadBalancer> balancer_;
|
||||||
std::shared_ptr<ReportingETL const> etl_;
|
std::shared_ptr<ReportingETL const> etl_;
|
||||||
util::TagDecoratorFactory const& tagFactory_;
|
util::TagDecoratorFactory const& tagFactory_;
|
||||||
DOSGuard& dosGuard_;
|
clio::DOSGuard& dosGuard_;
|
||||||
RPC::Counters& counters_;
|
RPC::Counters& counters_;
|
||||||
WorkQueue& workQueue_;
|
WorkQueue& workQueue_;
|
||||||
send_lambda lambda_;
|
send_lambda lambda_;
|
||||||
@@ -172,7 +172,7 @@ public:
|
|||||||
std::shared_ptr<ETLLoadBalancer> balancer,
|
std::shared_ptr<ETLLoadBalancer> balancer,
|
||||||
std::shared_ptr<ReportingETL const> etl,
|
std::shared_ptr<ReportingETL const> etl,
|
||||||
util::TagDecoratorFactory const& tagFactory,
|
util::TagDecoratorFactory const& tagFactory,
|
||||||
DOSGuard& dosGuard,
|
clio::DOSGuard& dosGuard,
|
||||||
RPC::Counters& counters,
|
RPC::Counters& counters,
|
||||||
WorkQueue& queue,
|
WorkQueue& queue,
|
||||||
boost::beast::flat_buffer buffer)
|
boost::beast::flat_buffer buffer)
|
||||||
@@ -197,7 +197,7 @@ public:
|
|||||||
perfLog_.debug() << tag() << "http session closed";
|
perfLog_.debug() << tag() << "http session closed";
|
||||||
}
|
}
|
||||||
|
|
||||||
DOSGuard&
|
clio::DOSGuard&
|
||||||
dosGuard()
|
dosGuard()
|
||||||
{
|
{
|
||||||
return dosGuard_;
|
return dosGuard_;
|
||||||
@@ -348,7 +348,7 @@ handle_request(
|
|||||||
std::shared_ptr<ETLLoadBalancer> balancer,
|
std::shared_ptr<ETLLoadBalancer> balancer,
|
||||||
std::shared_ptr<ReportingETL const> etl,
|
std::shared_ptr<ReportingETL const> etl,
|
||||||
util::TagDecoratorFactory const& tagFactory,
|
util::TagDecoratorFactory const& tagFactory,
|
||||||
DOSGuard& dosGuard,
|
clio::DOSGuard& dosGuard,
|
||||||
RPC::Counters& counters,
|
RPC::Counters& counters,
|
||||||
std::string const& ip,
|
std::string const& ip,
|
||||||
std::shared_ptr<Session> http,
|
std::shared_ptr<Session> http,
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ public:
|
|||||||
std::shared_ptr<ETLLoadBalancer> balancer,
|
std::shared_ptr<ETLLoadBalancer> balancer,
|
||||||
std::shared_ptr<ReportingETL const> etl,
|
std::shared_ptr<ReportingETL const> etl,
|
||||||
util::TagDecoratorFactory const& tagFactory,
|
util::TagDecoratorFactory const& tagFactory,
|
||||||
DOSGuard& dosGuard,
|
clio::DOSGuard& dosGuard,
|
||||||
RPC::Counters& counters,
|
RPC::Counters& counters,
|
||||||
WorkQueue& queue,
|
WorkQueue& queue,
|
||||||
boost::beast::flat_buffer buffer)
|
boost::beast::flat_buffer buffer)
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ class Detector
|
|||||||
std::shared_ptr<ETLLoadBalancer> balancer_;
|
std::shared_ptr<ETLLoadBalancer> balancer_;
|
||||||
std::shared_ptr<ReportingETL const> etl_;
|
std::shared_ptr<ReportingETL const> etl_;
|
||||||
util::TagDecoratorFactory const& tagFactory_;
|
util::TagDecoratorFactory const& tagFactory_;
|
||||||
DOSGuard& dosGuard_;
|
clio::DOSGuard& dosGuard_;
|
||||||
RPC::Counters& counters_;
|
RPC::Counters& counters_;
|
||||||
WorkQueue& queue_;
|
WorkQueue& queue_;
|
||||||
boost::beast::flat_buffer buffer_;
|
boost::beast::flat_buffer buffer_;
|
||||||
@@ -66,7 +66,7 @@ public:
|
|||||||
std::shared_ptr<ETLLoadBalancer> balancer,
|
std::shared_ptr<ETLLoadBalancer> balancer,
|
||||||
std::shared_ptr<ReportingETL const> etl,
|
std::shared_ptr<ReportingETL const> etl,
|
||||||
util::TagDecoratorFactory const& tagFactory,
|
util::TagDecoratorFactory const& tagFactory,
|
||||||
DOSGuard& dosGuard,
|
clio::DOSGuard& dosGuard,
|
||||||
RPC::Counters& counters,
|
RPC::Counters& counters,
|
||||||
WorkQueue& queue)
|
WorkQueue& queue)
|
||||||
: ioc_(ioc)
|
: ioc_(ioc)
|
||||||
@@ -164,7 +164,7 @@ make_websocket_session(
|
|||||||
std::shared_ptr<ETLLoadBalancer> balancer,
|
std::shared_ptr<ETLLoadBalancer> balancer,
|
||||||
std::shared_ptr<ReportingETL const> etl,
|
std::shared_ptr<ReportingETL const> etl,
|
||||||
util::TagDecoratorFactory const& tagFactory,
|
util::TagDecoratorFactory const& tagFactory,
|
||||||
DOSGuard& dosGuard,
|
clio::DOSGuard& dosGuard,
|
||||||
RPC::Counters& counters,
|
RPC::Counters& counters,
|
||||||
WorkQueue& queue)
|
WorkQueue& queue)
|
||||||
{
|
{
|
||||||
@@ -197,7 +197,7 @@ make_websocket_session(
|
|||||||
std::shared_ptr<ETLLoadBalancer> balancer,
|
std::shared_ptr<ETLLoadBalancer> balancer,
|
||||||
std::shared_ptr<ReportingETL const> etl,
|
std::shared_ptr<ReportingETL const> etl,
|
||||||
util::TagDecoratorFactory const& tagFactory,
|
util::TagDecoratorFactory const& tagFactory,
|
||||||
DOSGuard& dosGuard,
|
clio::DOSGuard& dosGuard,
|
||||||
RPC::Counters& counters,
|
RPC::Counters& counters,
|
||||||
WorkQueue& queue)
|
WorkQueue& queue)
|
||||||
{
|
{
|
||||||
@@ -234,7 +234,7 @@ class Listener
|
|||||||
std::shared_ptr<ETLLoadBalancer> balancer_;
|
std::shared_ptr<ETLLoadBalancer> balancer_;
|
||||||
std::shared_ptr<ReportingETL const> etl_;
|
std::shared_ptr<ReportingETL const> etl_;
|
||||||
util::TagDecoratorFactory tagFactory_;
|
util::TagDecoratorFactory tagFactory_;
|
||||||
DOSGuard& dosGuard_;
|
clio::DOSGuard& dosGuard_;
|
||||||
WorkQueue queue_;
|
WorkQueue queue_;
|
||||||
RPC::Counters counters_;
|
RPC::Counters counters_;
|
||||||
|
|
||||||
@@ -250,7 +250,7 @@ public:
|
|||||||
std::shared_ptr<ETLLoadBalancer> balancer,
|
std::shared_ptr<ETLLoadBalancer> balancer,
|
||||||
std::shared_ptr<ReportingETL const> etl,
|
std::shared_ptr<ReportingETL const> etl,
|
||||||
util::TagDecoratorFactory tagFactory,
|
util::TagDecoratorFactory tagFactory,
|
||||||
DOSGuard& dosGuard)
|
clio::DOSGuard& dosGuard)
|
||||||
: ioc_(ioc)
|
: ioc_(ioc)
|
||||||
, ctx_(ctx)
|
, ctx_(ctx)
|
||||||
, acceptor_(net::make_strand(ioc))
|
, acceptor_(net::make_strand(ioc))
|
||||||
@@ -356,7 +356,7 @@ make_HttpServer(
|
|||||||
std::shared_ptr<SubscriptionManager> subscriptions,
|
std::shared_ptr<SubscriptionManager> subscriptions,
|
||||||
std::shared_ptr<ETLLoadBalancer> balancer,
|
std::shared_ptr<ETLLoadBalancer> balancer,
|
||||||
std::shared_ptr<ReportingETL const> etl,
|
std::shared_ptr<ReportingETL const> etl,
|
||||||
DOSGuard& dosGuard)
|
clio::DOSGuard& dosGuard)
|
||||||
{
|
{
|
||||||
static clio::Logger log{"WebServer"};
|
static clio::Logger log{"WebServer"};
|
||||||
if (!config.contains("server"))
|
if (!config.contains("server"))
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ public:
|
|||||||
std::shared_ptr<ETLLoadBalancer> balancer,
|
std::shared_ptr<ETLLoadBalancer> balancer,
|
||||||
std::shared_ptr<ReportingETL const> etl,
|
std::shared_ptr<ReportingETL const> etl,
|
||||||
util::TagDecoratorFactory const& tagFactory,
|
util::TagDecoratorFactory const& tagFactory,
|
||||||
DOSGuard& dosGuard,
|
clio::DOSGuard& dosGuard,
|
||||||
RPC::Counters& counters,
|
RPC::Counters& counters,
|
||||||
WorkQueue& queue,
|
WorkQueue& queue,
|
||||||
boost::beast::flat_buffer&& buffer)
|
boost::beast::flat_buffer&& buffer)
|
||||||
@@ -102,7 +102,7 @@ class WsUpgrader : public std::enable_shared_from_this<WsUpgrader>
|
|||||||
std::shared_ptr<ETLLoadBalancer> balancer_;
|
std::shared_ptr<ETLLoadBalancer> balancer_;
|
||||||
std::shared_ptr<ReportingETL const> etl_;
|
std::shared_ptr<ReportingETL const> etl_;
|
||||||
util::TagDecoratorFactory const& tagFactory_;
|
util::TagDecoratorFactory const& tagFactory_;
|
||||||
DOSGuard& dosGuard_;
|
clio::DOSGuard& dosGuard_;
|
||||||
RPC::Counters& counters_;
|
RPC::Counters& counters_;
|
||||||
WorkQueue& queue_;
|
WorkQueue& queue_;
|
||||||
http::request<http::string_body> req_;
|
http::request<http::string_body> req_;
|
||||||
@@ -118,7 +118,7 @@ public:
|
|||||||
std::shared_ptr<ETLLoadBalancer> balancer,
|
std::shared_ptr<ETLLoadBalancer> balancer,
|
||||||
std::shared_ptr<ReportingETL const> etl,
|
std::shared_ptr<ReportingETL const> etl,
|
||||||
util::TagDecoratorFactory const& tagFactory,
|
util::TagDecoratorFactory const& tagFactory,
|
||||||
DOSGuard& dosGuard,
|
clio::DOSGuard& dosGuard,
|
||||||
RPC::Counters& counters,
|
RPC::Counters& counters,
|
||||||
WorkQueue& queue,
|
WorkQueue& queue,
|
||||||
boost::beast::flat_buffer&& b)
|
boost::beast::flat_buffer&& b)
|
||||||
@@ -145,7 +145,7 @@ public:
|
|||||||
std::shared_ptr<ETLLoadBalancer> balancer,
|
std::shared_ptr<ETLLoadBalancer> balancer,
|
||||||
std::shared_ptr<ReportingETL const> etl,
|
std::shared_ptr<ReportingETL const> etl,
|
||||||
util::TagDecoratorFactory const& tagFactory,
|
util::TagDecoratorFactory const& tagFactory,
|
||||||
DOSGuard& dosGuard,
|
clio::DOSGuard& dosGuard,
|
||||||
RPC::Counters& counters,
|
RPC::Counters& counters,
|
||||||
WorkQueue& queue,
|
WorkQueue& queue,
|
||||||
boost::beast::flat_buffer&& b,
|
boost::beast::flat_buffer&& b,
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ public:
|
|||||||
std::shared_ptr<ETLLoadBalancer> balancer,
|
std::shared_ptr<ETLLoadBalancer> balancer,
|
||||||
std::shared_ptr<ReportingETL const> etl,
|
std::shared_ptr<ReportingETL const> etl,
|
||||||
util::TagDecoratorFactory const& tagFactory,
|
util::TagDecoratorFactory const& tagFactory,
|
||||||
DOSGuard& dosGuard,
|
clio::DOSGuard& dosGuard,
|
||||||
RPC::Counters& counters,
|
RPC::Counters& counters,
|
||||||
WorkQueue& queue,
|
WorkQueue& queue,
|
||||||
boost::beast::flat_buffer buffer)
|
boost::beast::flat_buffer buffer)
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ public:
|
|||||||
std::shared_ptr<ETLLoadBalancer> balancer,
|
std::shared_ptr<ETLLoadBalancer> balancer,
|
||||||
std::shared_ptr<ReportingETL const> etl,
|
std::shared_ptr<ReportingETL const> etl,
|
||||||
util::TagDecoratorFactory const& tagFactory,
|
util::TagDecoratorFactory const& tagFactory,
|
||||||
DOSGuard& dosGuard,
|
clio::DOSGuard& dosGuard,
|
||||||
RPC::Counters& counters,
|
RPC::Counters& counters,
|
||||||
WorkQueue& queue,
|
WorkQueue& queue,
|
||||||
boost::beast::flat_buffer&& b)
|
boost::beast::flat_buffer&& b)
|
||||||
@@ -99,7 +99,7 @@ class SslWsUpgrader : public std::enable_shared_from_this<SslWsUpgrader>
|
|||||||
std::shared_ptr<ETLLoadBalancer> balancer_;
|
std::shared_ptr<ETLLoadBalancer> balancer_;
|
||||||
std::shared_ptr<ReportingETL const> etl_;
|
std::shared_ptr<ReportingETL const> etl_;
|
||||||
util::TagDecoratorFactory const& tagFactory_;
|
util::TagDecoratorFactory const& tagFactory_;
|
||||||
DOSGuard& dosGuard_;
|
clio::DOSGuard& dosGuard_;
|
||||||
RPC::Counters& counters_;
|
RPC::Counters& counters_;
|
||||||
WorkQueue& queue_;
|
WorkQueue& queue_;
|
||||||
http::request<http::string_body> req_;
|
http::request<http::string_body> req_;
|
||||||
@@ -115,7 +115,7 @@ public:
|
|||||||
std::shared_ptr<ETLLoadBalancer> balancer,
|
std::shared_ptr<ETLLoadBalancer> balancer,
|
||||||
std::shared_ptr<ReportingETL const> etl,
|
std::shared_ptr<ReportingETL const> etl,
|
||||||
util::TagDecoratorFactory const& tagFactory,
|
util::TagDecoratorFactory const& tagFactory,
|
||||||
DOSGuard& dosGuard,
|
clio::DOSGuard& dosGuard,
|
||||||
RPC::Counters& counters,
|
RPC::Counters& counters,
|
||||||
WorkQueue& queue,
|
WorkQueue& queue,
|
||||||
boost::beast::flat_buffer&& b)
|
boost::beast::flat_buffer&& b)
|
||||||
@@ -142,7 +142,7 @@ public:
|
|||||||
std::shared_ptr<ETLLoadBalancer> balancer,
|
std::shared_ptr<ETLLoadBalancer> balancer,
|
||||||
std::shared_ptr<ReportingETL const> etl,
|
std::shared_ptr<ReportingETL const> etl,
|
||||||
util::TagDecoratorFactory const& tagFactory,
|
util::TagDecoratorFactory const& tagFactory,
|
||||||
DOSGuard& dosGuard,
|
clio::DOSGuard& dosGuard,
|
||||||
RPC::Counters& counters,
|
RPC::Counters& counters,
|
||||||
WorkQueue& queue,
|
WorkQueue& queue,
|
||||||
boost::beast::flat_buffer&& b,
|
boost::beast::flat_buffer&& b,
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ class WsSession : public WsBase,
|
|||||||
std::shared_ptr<ETLLoadBalancer> balancer_;
|
std::shared_ptr<ETLLoadBalancer> balancer_;
|
||||||
std::shared_ptr<ReportingETL const> etl_;
|
std::shared_ptr<ReportingETL const> etl_;
|
||||||
util::TagDecoratorFactory const& tagFactory_;
|
util::TagDecoratorFactory const& tagFactory_;
|
||||||
DOSGuard& dosGuard_;
|
clio::DOSGuard& dosGuard_;
|
||||||
RPC::Counters& counters_;
|
RPC::Counters& counters_;
|
||||||
WorkQueue& queue_;
|
WorkQueue& queue_;
|
||||||
std::mutex mtx_;
|
std::mutex mtx_;
|
||||||
@@ -155,7 +155,7 @@ public:
|
|||||||
std::shared_ptr<ETLLoadBalancer> balancer,
|
std::shared_ptr<ETLLoadBalancer> balancer,
|
||||||
std::shared_ptr<ReportingETL const> etl,
|
std::shared_ptr<ReportingETL const> etl,
|
||||||
util::TagDecoratorFactory const& tagFactory,
|
util::TagDecoratorFactory const& tagFactory,
|
||||||
DOSGuard& dosGuard,
|
clio::DOSGuard& dosGuard,
|
||||||
RPC::Counters& counters,
|
RPC::Counters& counters,
|
||||||
WorkQueue& queue,
|
WorkQueue& queue,
|
||||||
boost::beast::flat_buffer&& buffer)
|
boost::beast::flat_buffer&& buffer)
|
||||||
|
|||||||
152
unittests/DOSGuard.cpp
Normal file
152
unittests/DOSGuard.cpp
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
This file is part of clio: https://github.com/XRPLF/clio
|
||||||
|
Copyright (c) 2022, the clio developers.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
#include <util/Fixtures.h>
|
||||||
|
|
||||||
|
#include <config/Config.h>
|
||||||
|
#include <webserver/DOSGuard.h>
|
||||||
|
|
||||||
|
#include <boost/json/parse.hpp>
|
||||||
|
#include <gmock/gmock.h>
|
||||||
|
|
||||||
|
using namespace testing;
|
||||||
|
using namespace clio;
|
||||||
|
using namespace std;
|
||||||
|
namespace json = boost::json;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr static auto JSONData = R"JSON(
|
||||||
|
{
|
||||||
|
"dos_guard": {
|
||||||
|
"max_fetches": 100,
|
||||||
|
"sweep_interval": 1,
|
||||||
|
"max_connections": 2,
|
||||||
|
"whitelist": ["127.0.0.1"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)JSON";
|
||||||
|
|
||||||
|
constexpr static auto JSONData2 = R"JSON(
|
||||||
|
{
|
||||||
|
"dos_guard": {
|
||||||
|
"max_fetches": 100,
|
||||||
|
"sweep_interval": 0.1,
|
||||||
|
"max_connections": 2,
|
||||||
|
"whitelist": ["127.0.0.1"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)JSON";
|
||||||
|
|
||||||
|
constexpr static auto IP = "127.0.0.2";
|
||||||
|
|
||||||
|
class FakeSweepHandler
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
using guard_type = BasicDOSGuard<FakeSweepHandler>;
|
||||||
|
guard_type* dosGuard_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void
|
||||||
|
setup(guard_type* guard)
|
||||||
|
{
|
||||||
|
dosGuard_ = guard;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sweep()
|
||||||
|
{
|
||||||
|
dosGuard_->clear();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}; // namespace
|
||||||
|
|
||||||
|
class DOSGuardTest : public NoLoggerFixture
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
Config cfg{json::parse(JSONData)};
|
||||||
|
FakeSweepHandler sweepHandler;
|
||||||
|
BasicDOSGuard<FakeSweepHandler> guard{cfg, sweepHandler};
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(DOSGuardTest, Whitelisting)
|
||||||
|
{
|
||||||
|
EXPECT_TRUE(guard.isWhiteListed("127.0.0.1"));
|
||||||
|
EXPECT_FALSE(guard.isWhiteListed(IP));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DOSGuardTest, ConnectionCount)
|
||||||
|
{
|
||||||
|
EXPECT_TRUE(guard.isOk(IP));
|
||||||
|
guard.increment(IP); // one connection
|
||||||
|
EXPECT_TRUE(guard.isOk(IP));
|
||||||
|
guard.increment(IP); // two connections
|
||||||
|
EXPECT_TRUE(guard.isOk(IP));
|
||||||
|
guard.increment(IP); // > two connections, can't connect more
|
||||||
|
EXPECT_FALSE(guard.isOk(IP));
|
||||||
|
|
||||||
|
guard.decrement(IP);
|
||||||
|
EXPECT_TRUE(guard.isOk(IP)); // can connect again
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DOSGuardTest, FetchCount)
|
||||||
|
{
|
||||||
|
EXPECT_TRUE(guard.add(IP, 50)); // half of allowence
|
||||||
|
EXPECT_TRUE(guard.add(IP, 50)); // now fully charged
|
||||||
|
EXPECT_FALSE(guard.add(IP, 1)); // can't add even 1 anymore
|
||||||
|
EXPECT_FALSE(guard.isOk(IP));
|
||||||
|
|
||||||
|
guard.clear(); // force clear the above fetch count
|
||||||
|
EXPECT_TRUE(guard.isOk(IP)); // can fetch again
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DOSGuardTest, ClearFetchCountOnTimer)
|
||||||
|
{
|
||||||
|
EXPECT_TRUE(guard.add(IP, 50)); // half of allowence
|
||||||
|
EXPECT_TRUE(guard.add(IP, 50)); // now fully charged
|
||||||
|
EXPECT_FALSE(guard.add(IP, 1)); // can't add even 1 anymore
|
||||||
|
EXPECT_FALSE(guard.isOk(IP));
|
||||||
|
|
||||||
|
sweepHandler.sweep(); // pretend sweep called from timer
|
||||||
|
EXPECT_TRUE(guard.isOk(IP)); // can fetch again
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename SweepHandler>
|
||||||
|
struct BasicDOSGuardMock : public BaseDOSGuard
|
||||||
|
{
|
||||||
|
BasicDOSGuardMock(SweepHandler& handler)
|
||||||
|
{
|
||||||
|
handler.setup(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
MOCK_METHOD(void, clear, (), (noexcept, override));
|
||||||
|
};
|
||||||
|
|
||||||
|
class DOSGuardIntervalSweepHandlerTest : public SyncAsioContextTest
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
Config cfg{json::parse(JSONData2)};
|
||||||
|
IntervalSweepHandler sweepHandler{cfg, ctx};
|
||||||
|
BasicDOSGuardMock<IntervalSweepHandler> guard{sweepHandler};
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(DOSGuardIntervalSweepHandlerTest, SweepAfterInterval)
|
||||||
|
{
|
||||||
|
EXPECT_CALL(guard, clear()).Times(Exactly(2));
|
||||||
|
ctx.run_for(std::chrono::milliseconds(210));
|
||||||
|
}
|
||||||
@@ -21,6 +21,9 @@
|
|||||||
|
|
||||||
#include <ios>
|
#include <ios>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include <boost/asio.hpp>
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
#include <log/Logger.h>
|
#include <log/Logger.h>
|
||||||
@@ -107,3 +110,46 @@ protected:
|
|||||||
boost::log::core::get()->set_logging_enabled(false);
|
boost::log::core::get()->set_logging_enabled(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Fixture with an embedded boost::asio context running on a thread
|
||||||
|
*
|
||||||
|
* This is meant to be used as a base for other fixtures.
|
||||||
|
*/
|
||||||
|
struct AsyncAsioContextTest : public NoLoggerFixture
|
||||||
|
{
|
||||||
|
AsyncAsioContextTest()
|
||||||
|
{
|
||||||
|
work.emplace(ctx); // make sure ctx does not stop on its own
|
||||||
|
}
|
||||||
|
|
||||||
|
~AsyncAsioContextTest()
|
||||||
|
{
|
||||||
|
work.reset();
|
||||||
|
ctx.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
boost::asio::io_context ctx;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::optional<boost::asio::io_service::work> work;
|
||||||
|
std::jthread runner{[this] { ctx.run(); }};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Fixture with an embedded boost::asio context that is not running by
|
||||||
|
* default but can be progressed on the calling thread
|
||||||
|
*
|
||||||
|
* Use `run_for(duration)` etc. directly on `ctx`.
|
||||||
|
* This is meant to be used as a base for other fixtures.
|
||||||
|
*/
|
||||||
|
struct SyncAsioContextTest : public NoLoggerFixture
|
||||||
|
{
|
||||||
|
SyncAsioContextTest()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
boost::asio::io_context ctx;
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user