refactor: Create interface for DOSGuard (#1602)

For #919.
This commit is contained in:
Sergey Kuznetsov
2024-08-13 17:26:47 +01:00
committed by GitHub
parent 49e9d5eda0
commit b7c8ed7e3a
26 changed files with 535 additions and 270 deletions

View File

@@ -33,11 +33,11 @@
#include "util/config/Config.hpp"
#include "util/log/Logger.hpp"
#include "util/prometheus/Prometheus.hpp"
#include "web/DOSGuard.hpp"
#include "web/IntervalSweepHandler.hpp"
#include "web/RPCServerHandler.hpp"
#include "web/Server.hpp"
#include "web/WhitelistHandler.hpp"
#include "web/dosguard/DOSGuard.hpp"
#include "web/dosguard/IntervalSweepHandler.hpp"
#include "web/dosguard/WhitelistHandler.hpp"
#include <boost/asio/io_context.hpp>
@@ -93,9 +93,9 @@ ClioApplication::run()
boost::asio::io_context ioc{threads};
// Rate limiter, to prevent abuse
auto whitelistHandler = web::WhitelistHandler{config_};
auto dosGuard = web::DOSGuard{config_, whitelistHandler};
auto sweepHandler = web::IntervalSweepHandler{config_, ioc, dosGuard};
auto whitelistHandler = web::dosguard::WhitelistHandler{config_};
auto dosGuard = web::dosguard::DOSGuard{config_, whitelistHandler};
auto sweepHandler = web::dosguard::IntervalSweepHandler{config_, ioc, dosGuard};
// Interface to the database
auto backend = data::make_Backend(config_);

View File

@@ -28,7 +28,7 @@
#include "rpc/common/impl/ForwardingProxy.hpp"
#include "util/log/Logger.hpp"
#include "web/Context.hpp"
#include "web/DOSGuard.hpp"
#include "web/dosguard/DOSGuardInterface.hpp"
#include <boost/asio/spawn.hpp>
#include <boost/json.hpp>
@@ -65,7 +65,7 @@ class RPCEngine {
util::Logger log_{"RPC"};
std::shared_ptr<BackendInterface> backend_;
std::reference_wrapper<web::DOSGuard const> dosGuard_;
std::reference_wrapper<web::dosguard::DOSGuardInterface const> dosGuard_;
std::reference_wrapper<WorkQueue> workQueue_;
std::reference_wrapper<Counters> counters_;
@@ -87,7 +87,7 @@ public:
RPCEngine(
std::shared_ptr<BackendInterface> const& backend,
std::shared_ptr<etl::LoadBalancer> const& balancer,
web::DOSGuard const& dosGuard,
web::dosguard::DOSGuardInterface const& dosGuard,
WorkQueue& workQueue,
Counters& counters,
std::shared_ptr<HandlerProvider const> const& handlerProvider
@@ -116,7 +116,7 @@ public:
make_RPCEngine(
std::shared_ptr<BackendInterface> const& backend,
std::shared_ptr<etl::LoadBalancer> const& balancer,
web::DOSGuard const& dosGuard,
web::dosguard::DOSGuardInterface const& dosGuard,
WorkQueue& workQueue,
Counters& counters,
std::shared_ptr<HandlerProvider const> const& handlerProvider

View File

@@ -1,8 +1,15 @@
add_library(clio_web)
target_sources(
clio_web PRIVATE impl/AdminVerificationStrategy.cpp impl/ServerSslContext.cpp IntervalSweepHandler.cpp Resolver.cpp
clio_web
PRIVATE Resolver.cpp
Server.cpp
dosguard/DOSGuard.cpp
dosguard/IntervalSweepHandler.cpp
dosguard/WhitelistHandler.cpp
impl/AdminVerificationStrategy.cpp
impl/ServerSslContext.cpp
ng/Server.cpp
)
target_link_libraries(clio_web PUBLIC clio_util)

View File

@@ -20,8 +20,8 @@
#pragma once
#include "util/Taggable.hpp"
#include "web/DOSGuard.hpp"
#include "web/PlainWsSession.hpp"
#include "web/dosguard/DOSGuardInterface.hpp"
#include "web/impl/HttpBase.hpp"
#include "web/interface/ConnectionBase.hpp"
@@ -70,7 +70,7 @@ public:
std::string const& ip,
std::shared_ptr<impl::AdminVerificationStrategy> const& adminVerification,
std::reference_wrapper<util::TagDecoratorFactory const> tagFactory,
std::reference_wrapper<web::DOSGuard> dosGuard,
std::reference_wrapper<dosguard::DOSGuardInterface> dosGuard,
std::shared_ptr<HandlerType> const& handler,
boost::beast::flat_buffer buffer
)

View File

@@ -20,7 +20,7 @@
#pragma once
#include "util/Taggable.hpp"
#include "web/DOSGuard.hpp"
#include "web/dosguard/DOSGuardInterface.hpp"
#include "web/impl/WsBase.hpp"
#include "web/interface/ConnectionBase.hpp"
@@ -68,7 +68,7 @@ public:
boost::asio::ip::tcp::socket&& socket,
std::string ip,
std::reference_wrapper<util::TagDecoratorFactory const> tagFactory,
std::reference_wrapper<web::DOSGuard> dosGuard,
std::reference_wrapper<dosguard::DOSGuardInterface> dosGuard,
std::shared_ptr<HandlerType> const& handler,
boost::beast::flat_buffer&& buffer,
bool isAdmin
@@ -102,7 +102,7 @@ class WsUpgrader : public std::enable_shared_from_this<WsUpgrader<HandlerType>>
boost::optional<http::request_parser<http::string_body>> parser_;
boost::beast::flat_buffer buffer_;
std::reference_wrapper<util::TagDecoratorFactory const> tagFactory_;
std::reference_wrapper<web::DOSGuard> dosGuard_;
std::reference_wrapper<dosguard::DOSGuardInterface> dosGuard_;
http::request<http::string_body> req_;
std::string ip_;
std::shared_ptr<HandlerType> const handler_;
@@ -125,7 +125,7 @@ public:
boost::beast::tcp_stream&& stream,
std::string ip,
std::reference_wrapper<util::TagDecoratorFactory const> tagFactory,
std::reference_wrapper<web::DOSGuard> dosGuard,
std::reference_wrapper<dosguard::DOSGuardInterface> dosGuard,
std::shared_ptr<HandlerType> const& handler,
boost::beast::flat_buffer&& buffer,
http::request<http::string_body> request,

View File

@@ -21,9 +21,9 @@
#include "util/Taggable.hpp"
#include "util/log/Logger.hpp"
#include "web/DOSGuard.hpp"
#include "web/HttpSession.hpp"
#include "web/SslHttpSession.hpp"
#include "web/dosguard/DOSGuardInterface.hpp"
#include "web/impl/ServerSslContext.hpp"
#include "web/interface/Concepts.hpp"
@@ -91,7 +91,7 @@ class Detector : public std::enable_shared_from_this<Detector<PlainSessionType,
boost::beast::tcp_stream stream_;
std::optional<std::reference_wrapper<boost::asio::ssl::context>> ctx_;
std::reference_wrapper<util::TagDecoratorFactory const> tagFactory_;
std::reference_wrapper<web::DOSGuard> const dosGuard_;
std::reference_wrapper<dosguard::DOSGuardInterface> const dosGuard_;
std::shared_ptr<HandlerType> const handler_;
boost::beast::flat_buffer buffer_;
std::shared_ptr<impl::AdminVerificationStrategy> const adminVerification_;
@@ -111,7 +111,7 @@ public:
tcp::socket&& socket,
std::optional<std::reference_wrapper<boost::asio::ssl::context>> ctx,
std::reference_wrapper<util::TagDecoratorFactory const> tagFactory,
std::reference_wrapper<web::DOSGuard> dosGuard,
std::reference_wrapper<dosguard::DOSGuardInterface> dosGuard,
std::shared_ptr<HandlerType> handler,
std::shared_ptr<impl::AdminVerificationStrategy> adminVerification
)
@@ -213,7 +213,7 @@ class Server : public std::enable_shared_from_this<Server<PlainSessionType, SslS
std::reference_wrapper<boost::asio::io_context> ioc_;
std::optional<boost::asio::ssl::context> ctx_;
util::TagDecoratorFactory tagFactory_;
std::reference_wrapper<web::DOSGuard> dosGuard_;
std::reference_wrapper<dosguard::DOSGuardInterface> dosGuard_;
std::shared_ptr<HandlerType> handler_;
tcp::acceptor acceptor_;
std::shared_ptr<impl::AdminVerificationStrategy> adminVerification_;
@@ -235,7 +235,7 @@ public:
std::optional<boost::asio::ssl::context> ctx,
tcp::endpoint endpoint,
util::TagDecoratorFactory tagFactory,
web::DOSGuard& dosGuard,
dosguard::DOSGuardInterface& dosGuard,
std::shared_ptr<HandlerType> handler,
std::optional<std::string> adminPassword
)
@@ -327,7 +327,7 @@ static std::shared_ptr<HttpServer<HandlerType>>
make_HttpServer(
util::Config const& config,
boost::asio::io_context& ioc,
web::DOSGuard& dosGuard,
dosguard::DOSGuardInterface& dosGuard,
std::shared_ptr<HandlerType> const& handler
)
{

View File

@@ -20,9 +20,10 @@
#pragma once
#include "util/Taggable.hpp"
#include "web/DOSGuard.hpp"
#include "web/SslWsSession.hpp"
#include "web/dosguard/DOSGuardInterface.hpp"
#include "web/impl/HttpBase.hpp"
#include "web/interface/Concepts.hpp"
#include "web/interface/ConnectionBase.hpp"
#include <boost/asio/ip/tcp.hpp>
@@ -78,7 +79,7 @@ public:
std::shared_ptr<impl::AdminVerificationStrategy> const& adminVerification,
boost::asio::ssl::context& ctx,
std::reference_wrapper<util::TagDecoratorFactory const> tagFactory,
std::reference_wrapper<web::DOSGuard> dosGuard,
std::reference_wrapper<dosguard::DOSGuardInterface> dosGuard,
std::shared_ptr<HandlerType> const& handler,
boost::beast::flat_buffer buffer
)

View File

@@ -20,7 +20,7 @@
#pragma once
#include "util/Taggable.hpp"
#include "web/DOSGuard.hpp"
#include "web/dosguard/DOSGuardInterface.hpp"
#include "web/impl/WsBase.hpp"
#include "web/interface/ConnectionBase.hpp"
@@ -69,7 +69,7 @@ public:
boost::beast::ssl_stream<boost::beast::tcp_stream>&& stream,
std::string ip,
std::reference_wrapper<util::TagDecoratorFactory const> tagFactory,
std::reference_wrapper<web::DOSGuard> dosGuard,
std::reference_wrapper<dosguard::DOSGuardInterface> dosGuard,
std::shared_ptr<HandlerType> const& handler,
boost::beast::flat_buffer&& buffer,
bool isAdmin
@@ -102,7 +102,7 @@ class SslWsUpgrader : public std::enable_shared_from_this<SslWsUpgrader<HandlerT
boost::beast::flat_buffer buffer_;
std::string ip_;
std::reference_wrapper<util::TagDecoratorFactory const> tagFactory_;
std::reference_wrapper<web::DOSGuard> dosGuard_;
std::reference_wrapper<dosguard::DOSGuardInterface> dosGuard_;
std::shared_ptr<HandlerType> const handler_;
http::request<http::string_body> req_;
bool isAdmin_;
@@ -124,7 +124,7 @@ public:
boost::beast::ssl_stream<boost::beast::tcp_stream> stream,
std::string ip,
std::reference_wrapper<util::TagDecoratorFactory const> tagFactory,
std::reference_wrapper<web::DOSGuard> dosGuard,
std::reference_wrapper<dosguard::DOSGuardInterface> dosGuard,
std::shared_ptr<HandlerType> handler,
boost::beast::flat_buffer&& buffer,
http::request<http::string_body> request,

View File

@@ -0,0 +1,148 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2024, 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 "web/dosguard/DOSGuard.hpp"
#include "util/Assert.hpp"
#include "util/config/Config.hpp"
#include "util/log/Logger.hpp"
#include "web/dosguard/WhitelistHandlerInterface.hpp"
#include <boost/iterator/transform_iterator.hpp>
#include <cstdint>
#include <functional>
#include <mutex>
#include <string>
#include <string_view>
#include <unordered_set>
namespace web::dosguard {
DOSGuard::DOSGuard(util::Config const& config, WhitelistHandlerInterface const& whitelistHandler)
: whitelistHandler_{std::cref(whitelistHandler)}
, maxFetches_{config.valueOr("dos_guard.max_fetches", DEFAULT_MAX_FETCHES)}
, maxConnCount_{config.valueOr("dos_guard.max_connections", DEFAULT_MAX_CONNECTIONS)}
, maxRequestCount_{config.valueOr("dos_guard.max_requests", DEFAULT_MAX_REQUESTS)}
{
}
[[nodiscard]] bool
DOSGuard::isWhiteListed(std::string_view const ip) const noexcept
{
return whitelistHandler_.get().isWhiteListed(ip);
}
[[nodiscard]] bool
DOSGuard::isOk(std::string const& ip) const noexcept
{
if (whitelistHandler_.get().isWhiteListed(ip))
return true;
{
std::scoped_lock const lck(mtx_);
if (ipState_.find(ip) != ipState_.end()) {
auto [transferedByte, requests] = ipState_.at(ip);
if (transferedByte > maxFetches_ || requests > maxRequestCount_) {
LOG(log_.warn()) << "Dosguard: Client surpassed the rate limit. ip = " << ip
<< " Transfered Byte: " << transferedByte << "; Requests: " << requests;
return false;
}
}
auto it = ipConnCount_.find(ip);
if (it != ipConnCount_.end()) {
if (it->second > maxConnCount_) {
LOG(log_.warn()) << "Dosguard: Client surpassed the rate limit. ip = " << ip
<< " Concurrent connection: " << it->second;
return false;
}
}
}
return true;
}
void
DOSGuard::increment(std::string const& ip) noexcept
{
if (whitelistHandler_.get().isWhiteListed(ip))
return;
std::scoped_lock const lck{mtx_};
ipConnCount_[ip]++;
}
void
DOSGuard::decrement(std::string const& ip) noexcept
{
if (whitelistHandler_.get().isWhiteListed(ip))
return;
std::scoped_lock const lck{mtx_};
ASSERT(ipConnCount_[ip] > 0, "Connection count for ip {} can't be 0", ip);
ipConnCount_[ip]--;
if (ipConnCount_[ip] == 0)
ipConnCount_.erase(ip);
}
[[maybe_unused]] bool
DOSGuard::add(std::string const& ip, uint32_t numObjects) noexcept
{
if (whitelistHandler_.get().isWhiteListed(ip))
return true;
{
std::scoped_lock const lck(mtx_);
ipState_[ip].transferedByte += numObjects;
}
return isOk(ip);
}
[[maybe_unused]] bool
DOSGuard::request(std::string const& ip) noexcept
{
if (whitelistHandler_.get().isWhiteListed(ip))
return true;
{
std::scoped_lock const lck(mtx_);
ipState_[ip].requestsCount++;
}
return isOk(ip);
}
void
DOSGuard::clear() noexcept
{
std::scoped_lock const lck(mtx_);
ipState_.clear();
}
[[nodiscard]] std::unordered_set<std::string>
DOSGuard::getWhitelist(util::Config const& config)
{
using T = std::unordered_set<std::string> const;
auto whitelist = config.arrayOr("dos_guard.whitelist", {});
auto const transform = [](auto const& elem) { return elem.template value<std::string>(); };
return T{
boost::transform_iterator(std::begin(whitelist), transform),
boost::transform_iterator(std::end(whitelist), transform)
};
}
} // namespace web::dosguard

View File

@@ -19,11 +19,10 @@
#pragma once
#include "util/Assert.hpp"
#include "util/config/Config.hpp"
#include "util/log/Logger.hpp"
#include "web/IntervalSweepHandler.hpp"
#include "web/WhitelistHandler.hpp"
#include "web/dosguard/DOSGuardInterface.hpp"
#include "web/dosguard/WhitelistHandlerInterface.hpp"
#include <boost/asio.hpp>
#include <boost/iterator/transform_iterator.hpp>
@@ -31,48 +30,32 @@
#include <cstdint>
#include <functional>
#include <iterator>
#include <mutex>
#include <string>
#include <string_view>
#include <unordered_map>
#include <unordered_set>
namespace web {
/**
* @brief The interface of a denial of service guard.
*/
class BaseDOSGuard {
public:
virtual ~BaseDOSGuard() = default;
/**
* @brief Clears implementation-defined counters.
*/
virtual void
clear() noexcept = 0;
};
namespace web::dosguard {
/**
* @brief A simple denial of service guard used for rate limiting.
*
* @tparam WhitelistHandlerType The type of the whitelist handler
*/
template <typename WhitelistHandlerType>
class BasicDOSGuard : public BaseDOSGuard {
class DOSGuard : public DOSGuardInterface {
/**
* @brief Accumulated state per IP, state will be reset accordingly
*/
struct ClientState {
std::uint32_t transferedByte = 0; /**< Accumulated transfered byte */
std::uint32_t transferedByte = 0; /**< Accumulated transferred byte */
std::uint32_t requestsCount = 0; /**< Accumulated served requests count */
};
mutable std::mutex mtx_;
std::unordered_map<std::string, ClientState> ipState_;
std::unordered_map<std::string, std::uint32_t> ipConnCount_;
std::reference_wrapper<WhitelistHandlerType const> whitelistHandler_;
std::reference_wrapper<WhitelistHandlerInterface const> whitelistHandler_;
std::uint32_t const maxFetches_;
std::uint32_t const maxConnCount_;
@@ -90,13 +73,7 @@ public:
* @param config Clio config
* @param whitelistHandler Whitelist handler that checks whitelist for IP addresses
*/
BasicDOSGuard(util::Config const& config, WhitelistHandlerType const& whitelistHandler)
: whitelistHandler_{std::cref(whitelistHandler)}
, maxFetches_{config.valueOr("dos_guard.max_fetches", DEFAULT_MAX_FETCHES)}
, maxConnCount_{config.valueOr("dos_guard.max_connections", DEFAULT_MAX_CONNECTIONS)}
, maxRequestCount_{config.valueOr("dos_guard.max_requests", DEFAULT_MAX_REQUESTS)}
{
}
DOSGuard(util::Config const& config, WhitelistHandlerInterface const& whitelistHandler);
/**
* @brief Check whether an ip address is in the whitelist or not.
@@ -106,10 +83,7 @@ public:
* @return false
*/
[[nodiscard]] bool
isWhiteListed(std::string_view const ip) const noexcept
{
return whitelistHandler_.get().isWhiteListed(ip);
}
isWhiteListed(std::string_view const ip) const noexcept override;
/**
* @brief Check whether an ip address is currently rate limited or not.
@@ -119,32 +93,7 @@ public:
* @return false If rate limited and the request should not be processed
*/
[[nodiscard]] bool
isOk(std::string const& ip) const noexcept
{
if (whitelistHandler_.get().isWhiteListed(ip))
return true;
{
std::scoped_lock const lck(mtx_);
if (ipState_.find(ip) != ipState_.end()) {
auto [transferedByte, requests] = ipState_.at(ip);
if (transferedByte > maxFetches_ || requests > maxRequestCount_) {
LOG(log_.warn()) << "Dosguard: Client surpassed the rate limit. ip = " << ip
<< " Transfered Byte: " << transferedByte << "; Requests: " << requests;
return false;
}
}
auto it = ipConnCount_.find(ip);
if (it != ipConnCount_.end()) {
if (it->second > maxConnCount_) {
LOG(log_.warn()) << "Dosguard: Client surpassed the rate limit. ip = " << ip
<< " Concurrent connection: " << it->second;
return false;
}
}
}
return true;
}
isOk(std::string const& ip) const noexcept override;
/**
* @brief Increment connection count for the given ip address.
@@ -152,13 +101,7 @@ public:
* @param ip
*/
void
increment(std::string const& ip) noexcept
{
if (whitelistHandler_.get().isWhiteListed(ip))
return;
std::scoped_lock const lck{mtx_};
ipConnCount_[ip]++;
}
increment(std::string const& ip) noexcept override;
/**
* @brief Decrement connection count for the given ip address.
@@ -166,16 +109,7 @@ public:
* @param ip
*/
void
decrement(std::string const& ip) noexcept
{
if (whitelistHandler_.get().isWhiteListed(ip))
return;
std::scoped_lock const lck{mtx_};
ASSERT(ipConnCount_[ip] > 0, "Connection count for ip {} can't be 0", ip);
ipConnCount_[ip]--;
if (ipConnCount_[ip] == 0)
ipConnCount_.erase(ip);
}
decrement(std::string const& ip) noexcept override;
/**
* @brief Adds numObjects of usage for the given ip address.
@@ -190,18 +124,7 @@ public:
* @return false
*/
[[maybe_unused]] bool
add(std::string const& ip, uint32_t numObjects) noexcept
{
if (whitelistHandler_.get().isWhiteListed(ip))
return true;
{
std::scoped_lock const lck(mtx_);
ipState_[ip].transferedByte += numObjects;
}
return isOk(ip);
}
add(std::string const& ip, uint32_t numObjects) noexcept override;
/**
* @brief Adds one request for the given ip address.
@@ -215,46 +138,17 @@ public:
* @return false
*/
[[maybe_unused]] bool
request(std::string const& ip) noexcept
{
if (whitelistHandler_.get().isWhiteListed(ip))
return true;
{
std::scoped_lock const lck(mtx_);
ipState_[ip].requestsCount++;
}
return isOk(ip);
}
request(std::string const& ip) noexcept override;
/**
* @brief Instantly clears all fetch counters added by @see add(std::string const&, uint32_t).
*/
void
clear() noexcept override
{
std::scoped_lock const lck(mtx_);
ipState_.clear();
}
clear() noexcept override;
private:
[[nodiscard]] std::unordered_set<std::string>
getWhitelist(util::Config const& config) const
{
using T = std::unordered_set<std::string> const;
auto whitelist = config.arrayOr("dos_guard.whitelist", {});
auto const transform = [](auto const& elem) { return elem.template value<std::string>(); };
return T{
boost::transform_iterator(std::begin(whitelist), transform),
boost::transform_iterator(std::end(whitelist), transform)
};
}
[[nodiscard]] static std::unordered_set<std::string>
getWhitelist(util::Config const& config);
};
/**
* @brief A simple denial of service guard used for rate limiting.
*/
using DOSGuard = BasicDOSGuard<web::WhitelistHandler>;
} // namespace web
} // namespace web::dosguard

View File

@@ -0,0 +1,110 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2024, 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.
*/
//==============================================================================
#pragma once
#include <cstdint>
#include <string>
#include <string_view>
namespace web::dosguard {
/**
* @brief The interface of a denial of service guard.
*/
class BaseDOSGuard {
public:
virtual ~BaseDOSGuard() = default;
/**
* @brief Clears implementation-defined counters.
*/
virtual void
clear() noexcept = 0;
};
/**
* @brief The interface of a denial of service guard.
*/
class DOSGuardInterface : public BaseDOSGuard {
public:
/**
* @brief Check whether an ip address is in the whitelist or not.
*
* @param ip The ip address to check
* @return true
* @return false
*/
[[nodiscard]] virtual bool
isWhiteListed(std::string_view const ip) const noexcept = 0;
/**
* @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]] virtual bool
isOk(std::string const& ip) const noexcept = 0;
/**
* @brief Increment connection count for the given ip address.
*
* @param ip
*/
virtual void
increment(std::string const& ip) noexcept = 0;
/**
* @brief Decrement connection count for the given ip address.
*
* @param ip
*/
virtual void
decrement(std::string const& ip) noexcept = 0;
/**
* @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]] virtual bool
add(std::string const& ip, uint32_t numObjects) noexcept = 0;
/**
* @brief Adds one request for the given ip address.
*
*
* @param ip
* @return If the total sums up to a value equal or larger than maxRequestCount_
* the operation is no longer allowed and false is returned; true is
* returned otherwise.
*/
[[maybe_unused]] virtual bool
request(std::string const& ip) noexcept = 0;
};
} // namespace web::dosguard

View File

@@ -17,10 +17,10 @@
*/
//==============================================================================
#include "web/IntervalSweepHandler.hpp"
#include "web/dosguard/IntervalSweepHandler.hpp"
#include "util/config/Config.hpp"
#include "web/DOSGuard.hpp"
#include "web/dosguard/DOSGuardInterface.hpp"
#include <boost/asio/io_context.hpp>
#include <boost/system/detail/error_code.hpp>
@@ -29,12 +29,12 @@
#include <chrono>
#include <functional>
namespace web {
namespace web::dosguard {
IntervalSweepHandler::IntervalSweepHandler(
util::Config const& config,
boost::asio::io_context& ctx,
web::BaseDOSGuard& dosGuard
BaseDOSGuard& dosGuard
)
: repeat_{std::ref(ctx)}
{
@@ -44,4 +44,4 @@ IntervalSweepHandler::IntervalSweepHandler(
repeat_.start(sweepInterval, [&dosGuard] { dosGuard.clear(); });
}
} // namespace web
} // namespace web::dosguard

View File

@@ -24,7 +24,7 @@
#include <boost/asio/io_context.hpp>
namespace web {
namespace web::dosguard {
class BaseDOSGuard;
@@ -42,7 +42,11 @@ public:
* @param ctx The boost::asio::io_context to use
* @param dosGuard The DOS guard to use
*/
IntervalSweepHandler(util::Config const& config, boost::asio::io_context& ctx, web::BaseDOSGuard& dosGuard);
IntervalSweepHandler(
util::Config const& config,
boost::asio::io_context& ctx,
web::dosguard::BaseDOSGuard& dosGuard
);
};
} // namespace web
} // namespace web::dosguard

View File

@@ -0,0 +1,118 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2024, 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 "web/dosguard/WhitelistHandler.hpp"
#include <boost/asio.hpp>
#include <boost/asio/ip/address.hpp>
#include <boost/asio/ip/network_v4.hpp>
#include <boost/asio/ip/network_v6.hpp>
#include <boost/iterator/transform_iterator.hpp>
#include <fmt/core.h>
#include <algorithm>
#include <functional>
#include <regex>
#include <stdexcept>
#include <string>
#include <string_view>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace web::dosguard {
void
Whitelist::add(std::string_view net)
{
using namespace boost::asio;
if (not isMask(net)) {
ips_.push_back(ip::make_address(net));
return;
}
if (isV4(net)) {
subnetsV4_.push_back(ip::make_network_v4(net));
} else if (isV6(net)) {
subnetsV6_.push_back(ip::make_network_v6(net));
} else {
throw std::runtime_error(fmt::format("malformed network: {}", net.data()));
}
}
bool
Whitelist::isWhiteListed(std::string_view ip) const
{
using namespace boost::asio;
auto const addr = ip::make_address(ip);
if (std::find(std::begin(ips_), std::end(ips_), addr) != std::end(ips_))
return true;
if (addr.is_v4()) {
return std::find_if(
std::begin(subnetsV4_), std::end(subnetsV4_), std::bind_front(&isInV4Subnet, std::cref(addr))
) != std::end(subnetsV4_);
}
if (addr.is_v6()) {
return std::find_if(
std::begin(subnetsV6_), std::end(subnetsV6_), std::bind_front(&isInV6Subnet, std::cref(addr))
) != std::end(subnetsV6_);
}
return false;
}
bool
Whitelist::isInV4Subnet(boost::asio::ip::address const& addr, boost::asio::ip::network_v4 const& subnet)
{
auto const range = subnet.hosts();
return range.find(addr.to_v4()) != range.end();
}
bool
Whitelist::isInV6Subnet(boost::asio::ip::address const& addr, boost::asio::ip::network_v6 const& subnet)
{
auto const range = subnet.hosts();
return range.find(addr.to_v6()) != range.end();
}
bool
Whitelist::isV4(std::string_view net)
{
static std::regex const ipv4CidrRegex(R"(^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/\d{1,2}$)");
return std::regex_match(std::string(net), ipv4CidrRegex);
}
bool
Whitelist::isV6(std::string_view net)
{
static std::regex const ipv6CidrRegex(R"(^([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}/\d{1,3}$)");
return std::regex_match(std::string(net), ipv6CidrRegex);
}
bool
Whitelist::isMask(std::string_view net)
{
return net.find('/') != std::string_view::npos;
}
} // namespace web::dosguard

View File

@@ -21,6 +21,7 @@
#include "util/config/Config.hpp"
#include "web/Resolver.hpp"
#include "web/dosguard/WhitelistHandlerInterface.hpp"
#include <boost/asio.hpp>
#include <boost/asio/ip/address.hpp>
@@ -30,16 +31,14 @@
#include <fmt/core.h>
#include <algorithm>
#include <functional>
#include <regex>
#include <stdexcept>
#include <string>
#include <string_view>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace web {
namespace web::dosguard {
/**
* @brief A whitelist to remove rate limits of certain IP addresses.
@@ -57,23 +56,7 @@ public:
* @throws std::runtime::error when the network address is not valid
*/
void
add(std::string_view net)
{
using namespace boost::asio;
if (not isMask(net)) {
ips_.push_back(ip::make_address(net));
return;
}
if (isV4(net)) {
subnetsV4_.push_back(ip::make_network_v4(net));
} else if (isV6(net)) {
subnetsV6_.push_back(ip::make_network_v6(net));
} else {
throw std::runtime_error(fmt::format("malformed network: {}", net.data()));
}
}
add(std::string_view net);
/**
* @brief Checks to see if ip address is whitelisted.
@@ -83,69 +66,29 @@ public:
* @return true if the given IP is whitelisted; false otherwise
*/
bool
isWhiteListed(std::string_view ip) const
{
using namespace boost::asio;
auto const addr = ip::make_address(ip);
if (std::find(std::begin(ips_), std::end(ips_), addr) != std::end(ips_))
return true;
if (addr.is_v4()) {
return std::find_if(
std::begin(subnetsV4_), std::end(subnetsV4_), std::bind_front(&isInV4Subnet, std::cref(addr))
) != std::end(subnetsV4_);
}
if (addr.is_v6()) {
return std::find_if(
std::begin(subnetsV6_), std::end(subnetsV6_), std::bind_front(&isInV6Subnet, std::cref(addr))
) != std::end(subnetsV6_);
}
return false;
}
isWhiteListed(std::string_view ip) const;
private:
static bool
isInV4Subnet(boost::asio::ip::address const& addr, boost::asio::ip::network_v4 const& subnet)
{
auto const range = subnet.hosts();
return range.find(addr.to_v4()) != range.end();
}
isInV4Subnet(boost::asio::ip::address const& addr, boost::asio::ip::network_v4 const& subnet);
static bool
isInV6Subnet(boost::asio::ip::address const& addr, boost::asio::ip::network_v6 const& subnet)
{
auto const range = subnet.hosts();
return range.find(addr.to_v6()) != range.end();
}
isInV6Subnet(boost::asio::ip::address const& addr, boost::asio::ip::network_v6 const& subnet);
static bool
isV4(std::string_view net)
{
static std::regex const ipv4CidrRegex(R"(^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/\d{1,2}$)");
return std::regex_match(std::string(net), ipv4CidrRegex);
}
isV4(std::string_view net);
static bool
isV6(std::string_view net)
{
static std::regex const ipv6CidrRegex(R"(^([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}/\d{1,3}$)");
return std::regex_match(std::string(net), ipv6CidrRegex);
}
isV6(std::string_view net);
static bool
isMask(std::string_view net)
{
return net.find('/') != std::string_view::npos;
}
isMask(std::string_view net);
};
/**
* @brief A simple handler to add/check elements in a whitelist.
*/
class WhitelistHandler {
class WhitelistHandler : public WhitelistHandlerInterface {
Whitelist whitelist_;
public:
@@ -170,7 +113,7 @@ public:
* @return true if the given IP is whitelisted; false otherwise
*/
bool
isWhiteListed(std::string_view ip) const
isWhiteListed(std::string_view ip) const override
{
return whitelist_.isWhiteListed(ip);
}
@@ -200,4 +143,4 @@ private:
}
};
} // namespace web
} // namespace web::dosguard

View File

@@ -0,0 +1,44 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2024, 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.
*/
//==============================================================================
#pragma once
#include <string_view>
namespace web::dosguard {
/**
* @brief Interface for a whitelist handler
*/
class WhitelistHandlerInterface {
public:
/** @brief Virtual destructor */
virtual ~WhitelistHandlerInterface() = default;
/**
* @brief Checks to see if the given IP is whitelisted
*
* @param ip The IP to check
* @return true if the given IP is whitelisted; false otherwise
*/
[[nodiscard]] virtual bool
isWhiteListed(std::string_view ip) const = 0;
};
} // namespace web::dosguard

View File

@@ -24,7 +24,7 @@
#include "util/build/Build.hpp"
#include "util/log/Logger.hpp"
#include "util/prometheus/Http.hpp"
#include "web/DOSGuard.hpp"
#include "web/dosguard/DOSGuardInterface.hpp"
#include "web/impl/AdminVerificationStrategy.hpp"
#include "web/interface/Concepts.hpp"
#include "web/interface/ConnectionBase.hpp"
@@ -114,7 +114,7 @@ class HttpBase : public ConnectionBase {
protected:
boost::beast::flat_buffer buffer_;
http::request<http::string_body> req_;
std::reference_wrapper<web::DOSGuard> dosGuard_;
std::reference_wrapper<dosguard::DOSGuardInterface> dosGuard_;
std::shared_ptr<HandlerType> const handler_;
util::Logger log_{"WebServer"};
util::Logger perfLog_{"Performance"};
@@ -154,7 +154,7 @@ public:
std::string const& ip,
std::reference_wrapper<util::TagDecoratorFactory const> tagFactory,
std::shared_ptr<AdminVerificationStrategy> adminVerification,
std::reference_wrapper<web::DOSGuard> dosGuard,
std::reference_wrapper<dosguard::DOSGuardInterface> dosGuard,
std::shared_ptr<HandlerType> handler,
boost::beast::flat_buffer buffer
)

View File

@@ -23,7 +23,7 @@
#include "rpc/common/Types.hpp"
#include "util/Taggable.hpp"
#include "util/log/Logger.hpp"
#include "web/DOSGuard.hpp"
#include "web/dosguard/DOSGuardInterface.hpp"
#include "web/interface/Concepts.hpp"
#include "web/interface/ConnectionBase.hpp"
@@ -72,7 +72,7 @@ class WsBase : public ConnectionBase, public std::enable_shared_from_this<WsBase
using std::enable_shared_from_this<WsBase<Derived, HandlerType>>::shared_from_this;
boost::beast::flat_buffer buffer_;
std::reference_wrapper<web::DOSGuard> dosGuard_;
std::reference_wrapper<dosguard::DOSGuardInterface> dosGuard_;
bool sending_ = false;
std::queue<std::shared_ptr<std::string>> messages_;
std::shared_ptr<HandlerType> const handler_;
@@ -99,7 +99,7 @@ public:
explicit WsBase(
std::string ip,
std::reference_wrapper<util::TagDecoratorFactory const> tagFactory,
std::reference_wrapper<web::DOSGuard> dosGuard,
std::reference_wrapper<dosguard::DOSGuardInterface> dosGuard,
std::shared_ptr<HandlerType> const& handler,
boost::beast::flat_buffer&& buffer
)

0
src/web/ng/Server.cpp Normal file
View File

0
src/web/ng/Server.hpp Normal file
View File

View File

@@ -23,7 +23,6 @@
#include "rpc/common/Specs.hpp"
#include "rpc/common/Types.hpp"
#include "rpc/common/Validators.hpp"
#include "web/DOSGuard.hpp"
#include <boost/json/conversion.hpp>
#include <boost/json/value.hpp>

View File

@@ -12,7 +12,6 @@ target_sources(
data/cassandra/ExecutionStrategyTests.cpp
data/cassandra/RetryPolicyTests.cpp
data/cassandra/SettingsProviderTests.cpp
DOSGuardTests.cpp
# ETL
etl/AmendmentBlockHandlerTests.cpp
etl/CacheLoaderSettingsTests.cpp
@@ -127,11 +126,12 @@ target_sources(
util/TxUtilTests.cpp
# Webserver
web/AdminVerificationTests.cpp
web/dosguard/DOSGuardTests.cpp
web/dosguard/IntervalSweepHandlerTests.cpp
web/dosguard/WhitelistHandlerTests.cpp
web/impl/ServerSslContextTests.cpp
web/RPCServerHandlerTests.cpp
web/ServerTests.cpp
web/IntervalSweepHandlerTests.cpp
web/WhitelistHandlerTests.cpp
# New Config
util/newconfig/ArrayViewTests.cpp
util/newconfig/ObjectViewTests.cpp

View File

@@ -24,10 +24,11 @@
#include "util/config/Config.hpp"
#include "util/prometheus/Label.hpp"
#include "util/prometheus/Prometheus.hpp"
#include "web/DOSGuard.hpp"
#include "web/IntervalSweepHandler.hpp"
#include "web/Server.hpp"
#include "web/WhitelistHandler.hpp"
#include "web/dosguard/DOSGuard.hpp"
#include "web/dosguard/DOSGuardInterface.hpp"
#include "web/dosguard/IntervalSweepHandler.hpp"
#include "web/dosguard/WhitelistHandler.hpp"
#include "web/impl/AdminVerificationStrategy.hpp"
#include "web/interface/ConnectionBase.hpp"
@@ -127,14 +128,14 @@ struct WebServerTest : NoLoggerFixture {
boost::asio::io_context ctxSync;
std::string const port = std::to_string(tests::util::generateFreePort());
Config cfg{generateJSONWithDynamicPort(port)};
WhitelistHandler whitelistHandler{cfg};
DOSGuard dosGuard{cfg, whitelistHandler};
IntervalSweepHandler sweepHandler{cfg, ctxSync, dosGuard};
dosguard::WhitelistHandler whitelistHandler{cfg};
dosguard::DOSGuard dosGuard{cfg, whitelistHandler};
dosguard::IntervalSweepHandler sweepHandler{cfg, ctxSync, dosGuard};
Config cfgOverload{generateJSONDataOverload(port)};
WhitelistHandler whitelistHandlerOverload{cfgOverload};
DOSGuard dosGuardOverload{cfgOverload, whitelistHandlerOverload};
IntervalSweepHandler sweepHandlerOverload{cfgOverload, ctxSync, dosGuardOverload};
dosguard::WhitelistHandler whitelistHandlerOverload{cfgOverload};
dosguard::DOSGuard dosGuardOverload{cfgOverload, whitelistHandlerOverload};
dosguard::IntervalSweepHandler sweepHandlerOverload{cfgOverload, ctxSync, dosGuardOverload};
// this ctx is for http server
boost::asio::io_context ctx;
@@ -178,7 +179,7 @@ std::shared_ptr<web::HttpServer<Executor>>
makeServerSync(
util::Config const& config,
boost::asio::io_context& ioc,
web::DOSGuard& dosGuard,
web::dosguard::DOSGuardInterface& dosGuard,
std::shared_ptr<Executor> const& handler
)
{

View File

@@ -19,7 +19,8 @@
#include "util/LoggerFixtures.hpp"
#include "util/config/Config.hpp"
#include "web/DOSGuard.hpp"
#include "web/dosguard/DOSGuard.hpp"
#include "web/dosguard/WhitelistHandlerInterface.hpp"
#include <boost/json/parse.hpp>
#include <gmock/gmock.h>
@@ -30,11 +31,11 @@
using namespace testing;
using namespace util;
using namespace std;
using namespace web;
using namespace web::dosguard;
namespace json = boost::json;
namespace {
constexpr auto JSONData = R"JSON(
struct DOSGuardTest : NoLoggerFixture {
static constexpr auto JSONData = R"JSON(
{
"dos_guard": {
"max_fetches": 100,
@@ -47,20 +48,15 @@ constexpr auto JSONData = R"JSON(
}
)JSON";
constexpr auto IP = "127.0.0.2";
static constexpr auto IP = "127.0.0.2";
struct MockWhitelistHandler {
struct MockWhitelistHandler : WhitelistHandlerInterface {
MOCK_METHOD(bool, isWhiteListed, (std::string_view ip), (const));
};
};
using MockWhitelistHandlerType = NiceMock<MockWhitelistHandler>;
}; // namespace
class DOSGuardTest : public NoLoggerFixture {
protected:
Config cfg{json::parse(JSONData)};
MockWhitelistHandlerType whitelistHandler;
BasicDOSGuard<MockWhitelistHandlerType> guard{cfg, whitelistHandler};
NiceMock<MockWhitelistHandler> whitelistHandler;
DOSGuard guard{cfg, whitelistHandler};
};
TEST_F(DOSGuardTest, Whitelisting)

View File

@@ -19,8 +19,8 @@
#include "util/AsioContextTestFixture.hpp"
#include "util/config/Config.hpp"
#include "web/DOSGuard.hpp"
#include "web/IntervalSweepHandler.hpp"
#include "web/dosguard/DOSGuardInterface.hpp"
#include "web/dosguard/IntervalSweepHandler.hpp"
#include <boost/json/parse.hpp>
#include <gmock/gmock.h>
@@ -28,7 +28,7 @@
#include <chrono>
using namespace web;
using namespace web::dosguard;
struct IntervalSweepHandlerTest : SyncAsioContextTest {
protected:

View File

@@ -18,7 +18,7 @@
//==============================================================================
#include "util/LoggerFixtures.hpp"
#include "util/config/Config.hpp"
#include "web/WhitelistHandler.hpp"
#include "web/dosguard/WhitelistHandler.hpp"
#include <boost/json/parse.hpp>
#include <gmock/gmock.h>
@@ -29,7 +29,7 @@
#include <vector>
using namespace util;
using namespace web;
using namespace web::dosguard;
struct WhitelistHandlerTest : NoLoggerFixture {};