feat: Dosguard API weights (#2082)

Experimental support for Dosguard API weights.
This commit is contained in:
Sergey Kuznetsov
2025-05-13 15:02:21 +01:00
committed by GitHub
parent d02da5d28a
commit b87b32db86
42 changed files with 1653 additions and 329 deletions

View File

@@ -31,6 +31,7 @@
#include "util/newconfig/Types.hpp"
#include "web/RPCServerHandler.hpp"
#include "web/SubscriptionContextInterface.hpp"
#include "web/dosguard/DOSGuardMock.hpp"
#include "web/interface/ConnectionBase.hpp"
#include <boost/beast/http/status.hpp>
@@ -39,6 +40,7 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <stdexcept>
@@ -58,6 +60,7 @@ constexpr auto kMAX_SEQ = 30;
struct MockWsBase : public web::ConnectionBase {
std::string message;
boost::beast::http::status lastStatus = boost::beast::http::status::unknown;
size_t slowDownCallsCounter{0};
void
send(std::shared_ptr<std::string> msgType) override
@@ -74,6 +77,12 @@ struct MockWsBase : public web::ConnectionBase {
lastStatus = status;
}
void
sendSlowDown(std::string const&) override
{
++slowDownCallsCounter;
}
SubscriptionContextPtr
makeSubscriptionContext(util::TagDecoratorFactory const&) override
{
@@ -94,9 +103,10 @@ struct WebRPCServerHandlerTest : util::prometheus::WithPrometheus, MockBackendTe
};
std::shared_ptr<MockAsyncRPCEngine> rpcEngine = std::make_shared<MockAsyncRPCEngine>();
std::shared_ptr<MockETLService> etl = std::make_shared<MockETLService>();
DOSGuardStrictMock dosguard;
std::shared_ptr<util::TagDecoratorFactory> tagFactory = std::make_shared<util::TagDecoratorFactory>(cfg);
std::shared_ptr<RPCServerHandler<MockAsyncRPCEngine>> handler =
std::make_shared<RPCServerHandler<MockAsyncRPCEngine>>(cfg, backend_, rpcEngine, etl);
std::make_shared<RPCServerHandler<MockAsyncRPCEngine>>(cfg, backend_, rpcEngine, etl, dosguard);
std::shared_ptr<MockWsBase> session = std::make_shared<MockWsBase>(*tagFactory);
};
@@ -121,6 +131,11 @@ TEST_F(WebRPCServerHandlerTest, HTTPDefaultPath)
}
]
})";
EXPECT_CALL(dosguard, isOk(session->clientIp)).WillOnce(testing::Return(true));
EXPECT_CALL(dosguard, request(session->clientIp, boost::json::parse(kREQUEST).as_object()))
.WillOnce(testing::Return(true));
EXPECT_CALL(*rpcEngine, buildResponse(testing::_))
.WillOnce(testing::Return(rpc::Result{boost::json::parse(kRESULT).as_object()}));
EXPECT_CALL(*rpcEngine, notifyComplete("server_info", testing::_)).Times(1);
@@ -131,6 +146,33 @@ TEST_F(WebRPCServerHandlerTest, HTTPDefaultPath)
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(kRESPONSE));
}
TEST_F(WebRPCServerHandlerTest, HTTPRejectedByDosguard)
{
static constexpr auto kREQUEST = R"({
"method": "server_info",
"params": [{}]
})";
EXPECT_CALL(dosguard, isOk(session->clientIp)).WillOnce(testing::Return(false));
(*handler)(kREQUEST, session);
EXPECT_EQ(session->slowDownCallsCounter, 1);
}
TEST_F(WebRPCServerHandlerTest, HTTPRejectedByDosguardAfterParsing)
{
static constexpr auto kREQUEST = R"({
"method": "server_info",
"params": [{}]
})";
EXPECT_CALL(dosguard, isOk(session->clientIp)).WillOnce(testing::Return(true));
EXPECT_CALL(dosguard, request(session->clientIp, testing::_)).WillOnce(testing::Return(false));
(*handler)(kREQUEST, session);
EXPECT_EQ(session->slowDownCallsCounter, 1);
}
TEST_F(WebRPCServerHandlerTest, WsNormalPath)
{
session->upgraded = true;
@@ -156,6 +198,10 @@ TEST_F(WebRPCServerHandlerTest, WsNormalPath)
}
]
})";
EXPECT_CALL(dosguard, isOk(session->clientIp)).WillOnce(testing::Return(true));
EXPECT_CALL(dosguard, request(session->clientIp, boost::json::parse(kREQUEST).as_object()))
.WillOnce(testing::Return(true));
EXPECT_CALL(*rpcEngine, buildResponse(testing::_))
.WillOnce(testing::Return(rpc::Result{boost::json::parse(kRESULT).as_object()}));
EXPECT_CALL(*rpcEngine, notifyComplete("server_info", testing::_)).Times(1);
@@ -166,6 +212,38 @@ TEST_F(WebRPCServerHandlerTest, WsNormalPath)
EXPECT_EQ(boost::json::parse(session->message), boost::json::parse(kRESPONSE));
}
TEST_F(WebRPCServerHandlerTest, WsRejectedByDosguard)
{
session->upgraded = true;
static constexpr auto kREQUEST = R"({
"command": "server_info",
"id": 99,
"api_version": 2
})";
EXPECT_CALL(dosguard, isOk(session->clientIp)).WillOnce(testing::Return(false));
(*handler)(kREQUEST, session);
EXPECT_EQ(session->slowDownCallsCounter, 1);
}
TEST_F(WebRPCServerHandlerTest, WsRejectedByDosguardAfterParsing)
{
session->upgraded = true;
static constexpr auto kREQUEST = R"({
"command": "server_info",
"id": 99,
"api_version": 2
})";
EXPECT_CALL(dosguard, isOk(session->clientIp)).WillOnce(testing::Return(true));
EXPECT_CALL(dosguard, request(session->clientIp, boost::json::parse(kREQUEST).as_object()))
.WillOnce(testing::Return(false));
(*handler)(kREQUEST, session);
EXPECT_EQ(session->slowDownCallsCounter, 1);
}
TEST_F(WebRPCServerHandlerTest, HTTPForwardedPath)
{
static constexpr auto kREQUEST = R"({
@@ -195,6 +273,11 @@ TEST_F(WebRPCServerHandlerTest, HTTPForwardedPath)
}
]
})";
EXPECT_CALL(dosguard, isOk(session->clientIp)).WillOnce(testing::Return(true));
EXPECT_CALL(dosguard, request(session->clientIp, boost::json::parse(kREQUEST).as_object()))
.WillOnce(testing::Return(true));
EXPECT_CALL(*rpcEngine, buildResponse(testing::_))
.WillOnce(testing::Return(rpc::Result{boost::json::parse(kRESULT).as_object()}));
EXPECT_CALL(*rpcEngine, notifyComplete("server_info", testing::_)).Times(1);
@@ -239,6 +322,11 @@ TEST_F(WebRPCServerHandlerTest, HTTPForwardedErrorPath)
}
]
})";
EXPECT_CALL(dosguard, isOk(session->clientIp)).WillOnce(testing::Return(true));
EXPECT_CALL(dosguard, request(session->clientIp, boost::json::parse(kREQUEST).as_object()))
.WillOnce(testing::Return(true));
EXPECT_CALL(*rpcEngine, buildResponse(testing::_))
.WillOnce(testing::Return(rpc::Result{boost::json::parse(kRESULT).as_object()}));
EXPECT_CALL(*rpcEngine, notifyComplete("server_info", testing::_)).Times(1);
@@ -281,6 +369,11 @@ TEST_F(WebRPCServerHandlerTest, WsForwardedPath)
}
]
})";
EXPECT_CALL(dosguard, isOk(session->clientIp)).WillOnce(testing::Return(true));
EXPECT_CALL(dosguard, request(session->clientIp, boost::json::parse(kREQUEST).as_object()))
.WillOnce(testing::Return(true));
EXPECT_CALL(*rpcEngine, buildResponse(testing::_))
.WillOnce(testing::Return(rpc::Result{boost::json::parse(kRESULT).as_object()}));
EXPECT_CALL(*rpcEngine, notifyComplete("server_info", testing::_)).Times(1);
@@ -326,6 +419,11 @@ TEST_F(WebRPCServerHandlerTest, WsForwardedErrorPath)
}
]
})";
EXPECT_CALL(dosguard, isOk(session->clientIp)).WillOnce(testing::Return(true));
EXPECT_CALL(dosguard, request(session->clientIp, boost::json::parse(kREQUEST).as_object()))
.WillOnce(testing::Return(true));
EXPECT_CALL(*rpcEngine, buildResponse(testing::_))
.WillOnce(testing::Return(rpc::Result{boost::json::parse(kRESULT).as_object()}));
@@ -373,6 +471,11 @@ TEST_F(WebRPCServerHandlerTest, HTTPErrorPath)
}
]
})";
EXPECT_CALL(dosguard, isOk(session->clientIp)).WillOnce(testing::Return(true));
EXPECT_CALL(dosguard, request(session->clientIp, boost::json::parse(kREQUEST_JSON).as_object()))
.WillOnce(testing::Return(true));
EXPECT_CALL(*rpcEngine, buildResponse(testing::_))
.WillOnce(testing::Return(rpc::Result{rpc::Status{rpc::RippledError::rpcINVALID_PARAMS, "ledgerIndexMalformed"}}
));
@@ -416,6 +519,11 @@ TEST_F(WebRPCServerHandlerTest, WsErrorPath)
"id": "123",
"api_version": 2
})";
EXPECT_CALL(dosguard, isOk(session->clientIp)).WillOnce(testing::Return(true));
EXPECT_CALL(dosguard, request(session->clientIp, boost::json::parse(kREQUEST_JSON).as_object()))
.WillOnce(testing::Return(true));
EXPECT_CALL(*rpcEngine, buildResponse(testing::_))
.WillOnce(testing::Return(rpc::Result{rpc::Status{rpc::RippledError::rpcINVALID_PARAMS, "ledgerIndexMalformed"}}
));
@@ -447,6 +555,10 @@ TEST_F(WebRPCServerHandlerTest, HTTPNotReady)
}
})";
EXPECT_CALL(dosguard, isOk(session->clientIp)).WillOnce(testing::Return(true));
EXPECT_CALL(dosguard, request(session->clientIp, boost::json::parse(kREQUEST).as_object()))
.WillOnce(testing::Return(true));
EXPECT_CALL(*rpcEngine, notifyNotReady).Times(1);
(*handler)(kREQUEST, session);
@@ -475,6 +587,10 @@ TEST_F(WebRPCServerHandlerTest, WsNotReady)
}
})";
EXPECT_CALL(dosguard, isOk(session->clientIp)).WillOnce(testing::Return(true));
EXPECT_CALL(dosguard, request(session->clientIp, boost::json::parse(kREQUEST).as_object()))
.WillOnce(testing::Return(true));
EXPECT_CALL(*rpcEngine, notifyNotReady).Times(1);
(*handler)(kREQUEST, session);
@@ -501,6 +617,9 @@ TEST_F(WebRPCServerHandlerTest, HTTPBadSyntaxWhenRequestSubscribe)
}
})";
EXPECT_CALL(dosguard, isOk(session->clientIp)).WillOnce(testing::Return(true));
EXPECT_CALL(dosguard, request(session->clientIp, testing::_)).WillOnce(testing::Return(true));
EXPECT_CALL(*rpcEngine, notifyBadSyntax).Times(1);
(*handler)(kREQUEST, session);
@@ -515,6 +634,9 @@ TEST_F(WebRPCServerHandlerTest, HTTPMissingCommand)
static constexpr auto kRESPONSE = "Null method";
EXPECT_CALL(dosguard, isOk(session->clientIp)).WillOnce(testing::Return(true));
EXPECT_CALL(dosguard, request(session->clientIp, testing::_)).WillOnce(testing::Return(true));
EXPECT_CALL(*rpcEngine, notifyBadSyntax).Times(1);
(*handler)(kREQUEST, session);
@@ -530,6 +652,9 @@ TEST_F(WebRPCServerHandlerTest, HTTPCommandNotString)
static constexpr auto kRESPONSE = "method is not string";
EXPECT_CALL(dosguard, isOk(session->clientIp)).WillOnce(testing::Return(true));
EXPECT_CALL(dosguard, request(session->clientIp, testing::_)).WillOnce(testing::Return(true));
EXPECT_CALL(*rpcEngine, notifyBadSyntax).Times(1);
(*handler)(kREQUEST, session);
@@ -545,6 +670,9 @@ TEST_F(WebRPCServerHandlerTest, HTTPCommandIsEmpty)
static constexpr auto kRESPONSE = "method is empty";
EXPECT_CALL(dosguard, isOk(session->clientIp)).WillOnce(testing::Return(true));
EXPECT_CALL(dosguard, request(session->clientIp, testing::_)).WillOnce(testing::Return(true));
EXPECT_CALL(*rpcEngine, notifyBadSyntax).Times(1);
(*handler)(kREQUEST, session);
@@ -575,6 +703,10 @@ TEST_F(WebRPCServerHandlerTest, WsMissingCommand)
}
})";
EXPECT_CALL(dosguard, isOk(session->clientIp)).WillOnce(testing::Return(true));
EXPECT_CALL(dosguard, request(session->clientIp, boost::json::parse(kREQUEST).as_object()))
.WillOnce(testing::Return(true));
EXPECT_CALL(*rpcEngine, notifyBadSyntax).Times(1);
(*handler)(kREQUEST, session);
@@ -592,6 +724,9 @@ TEST_F(WebRPCServerHandlerTest, HTTPParamsUnparsableNotArray)
"params": "wrong"
})";
EXPECT_CALL(dosguard, isOk(session->clientIp)).WillOnce(testing::Return(true));
EXPECT_CALL(dosguard, request(session->clientIp, testing::_)).WillOnce(testing::Return(true));
EXPECT_CALL(*rpcEngine, notifyBadSyntax).Times(1);
(*handler)(kREQUEST_JSON, session);
@@ -610,6 +745,9 @@ TEST_F(WebRPCServerHandlerTest, HTTPParamsUnparsableArrayWithDigit)
"params": [1]
})";
EXPECT_CALL(dosguard, isOk(session->clientIp)).WillOnce(testing::Return(true));
EXPECT_CALL(dosguard, request(session->clientIp, testing::_)).WillOnce(testing::Return(true));
EXPECT_CALL(*rpcEngine, notifyBadSyntax).Times(1);
(*handler)(kREQUEST_JSON, session);
@@ -640,6 +778,10 @@ TEST_F(WebRPCServerHandlerTest, HTTPInternalError)
"params": [{}]
})";
EXPECT_CALL(dosguard, isOk(session->clientIp)).WillOnce(testing::Return(true));
EXPECT_CALL(dosguard, request(session->clientIp, boost::json::parse(kREQUEST_JSON).as_object()))
.WillOnce(testing::Return(true));
EXPECT_CALL(*rpcEngine, notifyInternalError).Times(1);
EXPECT_CALL(*rpcEngine, buildResponse(testing::_)).Times(1).WillOnce(testing::Throw(std::runtime_error("MyError")));
@@ -671,6 +813,10 @@ TEST_F(WebRPCServerHandlerTest, WsInternalError)
"id": "123"
})";
EXPECT_CALL(dosguard, isOk(session->clientIp)).WillOnce(testing::Return(true));
EXPECT_CALL(dosguard, request(session->clientIp, boost::json::parse(kREQUEST_JSON).as_object()))
.WillOnce(testing::Return(true));
EXPECT_CALL(*rpcEngine, notifyInternalError).Times(1);
EXPECT_CALL(*rpcEngine, buildResponse(testing::_)).Times(1).WillOnce(testing::Throw(std::runtime_error("MyError")));
@@ -703,6 +849,11 @@ TEST_F(WebRPCServerHandlerTest, HTTPOutDated)
}
]
})";
EXPECT_CALL(dosguard, isOk(session->clientIp)).WillOnce(testing::Return(true));
EXPECT_CALL(dosguard, request(session->clientIp, boost::json::parse(kREQUEST).as_object()))
.WillOnce(testing::Return(true));
EXPECT_CALL(*rpcEngine, buildResponse(testing::_))
.WillOnce(testing::Return(rpc::Result{boost::json::parse(kRESULT).as_object()}));
EXPECT_CALL(*rpcEngine, notifyComplete("server_info", testing::_)).Times(1);
@@ -741,6 +892,11 @@ TEST_F(WebRPCServerHandlerTest, WsOutdated)
}
]
})";
EXPECT_CALL(dosguard, isOk(session->clientIp)).WillOnce(testing::Return(true));
EXPECT_CALL(dosguard, request(session->clientIp, boost::json::parse(kREQUEST).as_object()))
.WillOnce(testing::Return(true));
EXPECT_CALL(*rpcEngine, buildResponse(testing::_))
.WillOnce(testing::Return(rpc::Result{boost::json::parse(kRESULT).as_object()}));
EXPECT_CALL(*rpcEngine, notifyComplete("server_info", testing::_)).Times(1);
@@ -756,7 +912,7 @@ TEST_F(WebRPCServerHandlerTest, WsTooBusy)
session->upgraded = true;
auto localRpcEngine = std::make_shared<MockRPCEngine>();
auto localHandler = std::make_shared<RPCServerHandler<MockRPCEngine>>(cfg, backend_, localRpcEngine, etl);
auto localHandler = std::make_shared<RPCServerHandler<MockRPCEngine>>(cfg, backend_, localRpcEngine, etl, dosguard);
static constexpr auto kREQUEST = R"({
"command": "server_info",
"id": 99
@@ -773,6 +929,10 @@ TEST_F(WebRPCServerHandlerTest, WsTooBusy)
"type": "response"
})";
EXPECT_CALL(dosguard, isOk(session->clientIp)).WillOnce(testing::Return(true));
EXPECT_CALL(dosguard, request(session->clientIp, boost::json::parse(kREQUEST).as_object()))
.WillOnce(testing::Return(true));
EXPECT_CALL(*localRpcEngine, notifyTooBusy).Times(1);
EXPECT_CALL(*localRpcEngine, post).WillOnce(testing::Return(false));
@@ -783,7 +943,7 @@ TEST_F(WebRPCServerHandlerTest, WsTooBusy)
TEST_F(WebRPCServerHandlerTest, HTTPTooBusy)
{
auto localRpcEngine = std::make_shared<MockRPCEngine>();
auto localHandler = std::make_shared<RPCServerHandler<MockRPCEngine>>(cfg, backend_, localRpcEngine, etl);
auto localHandler = std::make_shared<RPCServerHandler<MockRPCEngine>>(cfg, backend_, localRpcEngine, etl, dosguard);
static constexpr auto kREQUEST = R"({
"method": "server_info",
"params": [{}]
@@ -800,6 +960,10 @@ TEST_F(WebRPCServerHandlerTest, HTTPTooBusy)
"type": "response"
})";
EXPECT_CALL(dosguard, isOk(session->clientIp)).WillOnce(testing::Return(true));
EXPECT_CALL(dosguard, request(session->clientIp, boost::json::parse(kREQUEST).as_object()))
.WillOnce(testing::Return(true));
EXPECT_CALL(*localRpcEngine, notifyTooBusy).Times(1);
EXPECT_CALL(*localRpcEngine, post).WillOnce(testing::Return(false));
@@ -812,6 +976,8 @@ TEST_F(WebRPCServerHandlerTest, HTTPRequestNotJson)
static constexpr auto kREQUEST = "not json";
static constexpr auto kRESPONSE_PREFIX = "Unable to parse JSON from the request";
EXPECT_CALL(dosguard, isOk(session->clientIp)).WillOnce(testing::Return(true));
EXPECT_CALL(*rpcEngine, notifyBadSyntax).Times(1);
(*handler)(kREQUEST, session);
@@ -832,6 +998,8 @@ TEST_F(WebRPCServerHandlerTest, WsRequestNotJson)
"type": "response"
})";
EXPECT_CALL(dosguard, isOk(session->clientIp)).WillOnce(testing::Return(true));
EXPECT_CALL(*rpcEngine, notifyBadSyntax).Times(1);
(*handler)(kREQUEST, session);
@@ -888,6 +1056,10 @@ TEST_P(WebRPCServerHandlerInvalidAPIVersionParamTest, HTTPInvalidAPIVersion)
backend_->setRange(kMIN_SEQ, kMAX_SEQ);
EXPECT_CALL(dosguard, isOk(session->clientIp)).WillOnce(testing::Return(true));
EXPECT_CALL(dosguard, request(session->clientIp, boost::json::parse(request).as_object()))
.WillOnce(testing::Return(true));
EXPECT_CALL(*rpcEngine, notifyBadSyntax).Times(1);
(*handler)(request, session);
@@ -908,6 +1080,10 @@ TEST_P(WebRPCServerHandlerInvalidAPIVersionParamTest, WSInvalidAPIVersion)
backend_->setRange(kMIN_SEQ, kMAX_SEQ);
EXPECT_CALL(dosguard, isOk(session->clientIp)).WillOnce(testing::Return(true));
EXPECT_CALL(dosguard, request(session->clientIp, boost::json::parse(request).as_object()))
.WillOnce(testing::Return(true));
EXPECT_CALL(*rpcEngine, notifyBadSyntax).Times(1);
(*handler)(request, session);