mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-18 16:16:55 +00:00
226 lines
6.7 KiB
C++
226 lines
6.7 KiB
C++
#include <test/jtx/Env.h>
|
|
|
|
#include <xrpld/app/main/Application.h>
|
|
#include <xrpld/overlay/Compression.h>
|
|
#include <xrpld/overlay/Message.h>
|
|
#include <xrpld/overlay/Peer.h>
|
|
#include <xrpld/overlay/detail/OverlayImpl.h>
|
|
#include <xrpld/overlay/detail/PeerImp.h>
|
|
#include <xrpld/overlay/detail/ProtocolVersion.h>
|
|
#include <xrpld/overlay/detail/Tuning.h>
|
|
#include <xrpld/peerfinder/Slot.h>
|
|
|
|
#include <xrpl/basics/Blob.h>
|
|
#include <xrpl/basics/base_uint.h>
|
|
#include <xrpl/basics/make_SSLContext.h>
|
|
#include <xrpl/beast/net/IPEndpoint.h>
|
|
#include <xrpl/beast/unit_test/suite.h>
|
|
#include <xrpl/nodestore/NodeObject.h>
|
|
#include <xrpl/protocol/KeyType.h>
|
|
#include <xrpl/protocol/PublicKey.h>
|
|
#include <xrpl/protocol/SecretKey.h>
|
|
#include <xrpl/protocol/digest.h>
|
|
#include <xrpl/resource/Consumer.h>
|
|
#include <xrpl/server/Handoff.h>
|
|
|
|
#include <boost/asio/ip/address.hpp>
|
|
#include <boost/asio/ip/tcp.hpp>
|
|
#include <boost/asio/ssl/context.hpp>
|
|
#include <boost/beast/core/tcp_stream.hpp>
|
|
#include <boost/beast/ssl/ssl_stream.hpp>
|
|
|
|
#include <xrpl.pb.h>
|
|
|
|
#include <cstddef>
|
|
#include <memory>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
namespace xrpl::test {
|
|
|
|
using namespace jtx;
|
|
|
|
/**
|
|
* Test for TMGetObjectByHash reply size limiting.
|
|
*
|
|
* This verifies the fix that limits TMGetObjectByHash replies to
|
|
* Tuning::hardMaxReplyNodes to prevent excessive memory usage and
|
|
* potential DoS attacks from peers requesting large numbers of objects.
|
|
*/
|
|
class TMGetObjectByHash_test : public beast::unit_test::Suite
|
|
{
|
|
using middle_type = boost::beast::tcp_stream;
|
|
using stream_type = boost::beast::ssl_stream<middle_type>;
|
|
using socket_type = boost::asio::ip::tcp::socket;
|
|
using shared_context = std::shared_ptr<boost::asio::ssl::context>;
|
|
/**
|
|
* Test peer that captures sent messages for verification.
|
|
*/
|
|
class PeerTest : public PeerImp
|
|
{
|
|
public:
|
|
PeerTest(
|
|
Application& app,
|
|
std::shared_ptr<PeerFinder::Slot> const& slot,
|
|
http_request_type&& request,
|
|
PublicKey const& publicKey,
|
|
ProtocolVersion protocol,
|
|
Resource::Consumer consumer,
|
|
std::unique_ptr<TMGetObjectByHash_test::stream_type>&& streamPtr,
|
|
OverlayImpl& overlay)
|
|
: PeerImp(
|
|
app,
|
|
id++,
|
|
slot,
|
|
std::move(request),
|
|
publicKey,
|
|
protocol,
|
|
consumer,
|
|
std::move(streamPtr),
|
|
overlay)
|
|
{
|
|
}
|
|
|
|
~PeerTest() override = default;
|
|
|
|
void
|
|
run() override
|
|
{
|
|
}
|
|
|
|
void
|
|
send(std::shared_ptr<Message> const& m) override
|
|
{
|
|
lastSentMessage_ = m;
|
|
}
|
|
|
|
std::shared_ptr<Message>
|
|
getLastSentMessage() const
|
|
{
|
|
return lastSentMessage_;
|
|
}
|
|
|
|
static void
|
|
resetId()
|
|
{
|
|
id = 0;
|
|
}
|
|
|
|
private:
|
|
inline static Peer::id_t id = 0;
|
|
std::shared_ptr<Message> lastSentMessage_;
|
|
};
|
|
|
|
shared_context context_{makeSslContext("")};
|
|
ProtocolVersion protocolVersion_{1, 7};
|
|
|
|
std::shared_ptr<PeerTest>
|
|
createPeer(jtx::Env& env)
|
|
{
|
|
auto& overlay = dynamic_cast<OverlayImpl&>(env.app().getOverlay());
|
|
boost::beast::http::request<boost::beast::http::dynamic_body> request;
|
|
auto streamPtr =
|
|
std::make_unique<stream_type>(socket_type(env.app().getIOContext()), *context_);
|
|
|
|
beast::IP::Endpoint const local(boost::asio::ip::make_address("172.1.1.1"), 51235);
|
|
beast::IP::Endpoint const remote(boost::asio::ip::make_address("172.1.1.2"), 51235);
|
|
|
|
PublicKey const key(std::get<0>(randomKeyPair(KeyType::Ed25519)));
|
|
auto consumer = overlay.resourceManager().newInboundEndpoint(remote);
|
|
auto [slot, _] = overlay.peerFinder().newInboundSlot(local, remote);
|
|
|
|
auto peer = std::make_shared<PeerTest>(
|
|
env.app(),
|
|
slot,
|
|
std::move(request),
|
|
key,
|
|
protocolVersion_,
|
|
consumer,
|
|
std::move(streamPtr),
|
|
overlay);
|
|
|
|
overlay.addActive(peer);
|
|
return peer;
|
|
}
|
|
|
|
static std::shared_ptr<protocol::TMGetObjectByHash>
|
|
createRequest(size_t const numObjects, Env& env)
|
|
{
|
|
// Store objects in the NodeStore that will be found during the query
|
|
auto& nodeStore = env.app().getNodeStore();
|
|
|
|
// Create and store objects
|
|
std::vector<uint256> hashes;
|
|
hashes.reserve(numObjects);
|
|
for (int i = 0; i < numObjects; ++i)
|
|
{
|
|
uint256 const hash(xrpl::sha512Half(i));
|
|
hashes.push_back(hash);
|
|
|
|
Blob data(100, static_cast<unsigned char>(i % 256));
|
|
nodeStore.store(
|
|
NodeObjectType::Ledger, std::move(data), hash, nodeStore.earliestLedgerSeq());
|
|
}
|
|
|
|
// Create a request with more objects than hardMaxReplyNodes
|
|
auto request = std::make_shared<protocol::TMGetObjectByHash>();
|
|
request->set_type(protocol::TMGetObjectByHash_ObjectType_otLEDGER);
|
|
request->set_query(true);
|
|
|
|
for (int i = 0; i < numObjects; ++i)
|
|
{
|
|
auto object = request->add_objects();
|
|
object->set_hash(hashes[i].data(), hashes[i].size());
|
|
object->set_ledgerseq(i);
|
|
}
|
|
return request;
|
|
}
|
|
|
|
/**
|
|
* Test that reply is limited to hardMaxReplyNodes when more objects
|
|
* are requested than the limit allows.
|
|
*/
|
|
void
|
|
testReplyLimit(size_t const numObjects, int const expectedReplySize)
|
|
{
|
|
testcase("Reply Limit");
|
|
|
|
Env env(*this);
|
|
PeerTest::resetId();
|
|
|
|
auto peer = createPeer(env);
|
|
|
|
auto request = createRequest(numObjects, env);
|
|
// Call the onMessage handler
|
|
peer->onMessage(request);
|
|
|
|
// Verify that a reply was sent
|
|
auto sentMessage = peer->getLastSentMessage();
|
|
BEAST_EXPECT(sentMessage != nullptr);
|
|
|
|
// Parse the reply message
|
|
auto const& buffer = sentMessage->getBuffer(compression::Compressed::Off);
|
|
|
|
BEAST_EXPECT(buffer.size() > 6);
|
|
// Skip the message header (6 bytes: 4 for size, 2 for type)
|
|
protocol::TMGetObjectByHash reply;
|
|
BEAST_EXPECT(reply.ParseFromArray(buffer.data() + 6, buffer.size() - 6) == true);
|
|
|
|
// Verify the reply is limited to expectedReplySize
|
|
BEAST_EXPECT(reply.objects_size() == expectedReplySize);
|
|
}
|
|
|
|
void
|
|
run() override
|
|
{
|
|
int const limit = static_cast<int>(Tuning::kHardMaxReplyNodes);
|
|
testReplyLimit(limit + 1, limit);
|
|
testReplyLimit(limit, limit);
|
|
testReplyLimit(limit - 1, limit - 1);
|
|
}
|
|
};
|
|
|
|
BEAST_DEFINE_TESTSUITE(TMGetObjectByHash, overlay, xrpl);
|
|
|
|
} // namespace xrpl::test
|