aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorLee *!* Clagett <code@leeclagett.com>2025-01-21 09:56:52 -0500
committerLee *!* Clagett <code@leeclagett.com>2025-02-14 00:21:05 -0500
commit13ff355cf6081776fd7379080c17246e35c1236d (patch)
tree7efc3ffd2c8288857d428e1eaf5d9615f77b0900 /tests
parent89fa3ed68ae1a21e71a35ad4c21afaa2baae1987 (diff)
downloadmonzero-core-13ff355cf6081776fd7379080c17246e35c1236d.tar.gz
monzero-core-13ff355cf6081776fd7379080c17246e35c1236d.tar.xz
monzero-core-13ff355cf6081776fd7379080c17246e35c1236d.zip
Set response limits on http server connections
Diffstat (limited to 'tests')
-rwxr-xr-xtests/functional_tests/functional_tests_rpc.py2
-rw-r--r--tests/unit_tests/CMakeLists.txt1
-rw-r--r--tests/unit_tests/epee_http_server.cpp200
3 files changed, 202 insertions, 1 deletions
diff --git a/tests/functional_tests/functional_tests_rpc.py b/tests/functional_tests/functional_tests_rpc.py
index f0203bad1..d8ec78810 100755
--- a/tests/functional_tests/functional_tests_rpc.py
+++ b/tests/functional_tests/functional_tests_rpc.py
@@ -48,7 +48,7 @@ WALLET_DIRECTORY = builddir + "/functional-tests-directory"
FUNCTIONAL_TESTS_DIRECTORY = builddir + "/tests/functional_tests"
DIFFICULTY = 10
-monerod_base = [builddir + "/bin/monerod", "--regtest", "--fixed-difficulty", str(DIFFICULTY), "--no-igd", "--p2p-bind-port", "monerod_p2p_port", "--rpc-bind-port", "monerod_rpc_port", "--zmq-rpc-bind-port", "monerod_zmq_port", "--zmq-pub", "monerod_zmq_pub", "--non-interactive", "--disable-dns-checkpoints", "--check-updates", "disabled", "--rpc-ssl", "disabled", "--data-dir", "monerod_data_dir", "--log-level", "1"]
+monerod_base = [builddir + "/bin/monerod", "--regtest", "--fixed-difficulty", str(DIFFICULTY), "--no-igd", "--p2p-bind-port", "monerod_p2p_port", "--rpc-bind-port", "monerod_rpc_port", "--zmq-rpc-bind-port", "monerod_zmq_port", "--zmq-pub", "monerod_zmq_pub", "--non-interactive", "--disable-dns-checkpoints", "--check-updates", "disabled", "--rpc-ssl", "disabled", "--data-dir", "monerod_data_dir", "--log-level", "1", "--rpc-max-connections-per-private-ip", "100", "--rpc-max-connections", "100"]
monerod_extra = [
["--offline"],
["--rpc-payment-address", "44SKxxLQw929wRF6BA9paQ1EWFshNnKhXM3qz6Mo3JGDE2YG3xyzVutMStEicxbQGRfrYvAAYxH6Fe8rnD56EaNwUiqhcwR", "--rpc-payment-difficulty", str(DIFFICULTY), "--rpc-payment-credits", "5000", "--offline"],
diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt
index fec36803e..2415dfea8 100644
--- a/tests/unit_tests/CMakeLists.txt
+++ b/tests/unit_tests/CMakeLists.txt
@@ -48,6 +48,7 @@ set(unit_tests_sources
dns_resolver.cpp
epee_boosted_tcp_server.cpp
epee_levin_protocol_handler_async.cpp
+ epee_http_server.cpp
epee_serialization.cpp
epee_utils.cpp
expect.cpp
diff --git a/tests/unit_tests/epee_http_server.cpp b/tests/unit_tests/epee_http_server.cpp
new file mode 100644
index 000000000..1d3b60d54
--- /dev/null
+++ b/tests/unit_tests/epee_http_server.cpp
@@ -0,0 +1,200 @@
+// Copyright (c) 2014-2024, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#include <atomic>
+#include <boost/asio/connect.hpp>
+#include <boost/asio/ip/tcp.hpp>
+#include <boost/beast/core.hpp>
+#include <boost/beast/http.hpp>
+#include <boost/beast/version.hpp>
+#include "gtest/gtest.h"
+#include "net/http_server_handlers_map2.h"
+#include "net/http_server_impl_base.h"
+#include "storages/portable_storage_template_helper.h"
+
+namespace
+{
+ constexpr const std::size_t payload_size = 26 * 1024 * 1024;
+ constexpr const std::size_t max_private_ips = 25;
+ struct dummy
+ {
+ struct request
+ {
+ BEGIN_KV_SERIALIZE_MAP()
+ END_KV_SERIALIZE_MAP()
+ };
+
+ struct response
+ {
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(payload)
+ END_KV_SERIALIZE_MAP()
+
+ std::string payload;
+ };
+ };
+
+ std::string make_payload()
+ {
+ dummy::request body{};
+ const auto body_serialized = epee::serialization::store_t_to_binary(body);
+ return std::string{
+ reinterpret_cast<const char*>(body_serialized.data()),
+ body_serialized.size()
+ };
+ }
+
+ struct http_server : epee::http_server_impl_base<http_server>
+ {
+ using connection_context = epee::net_utils::connection_context_base;
+
+ http_server()
+ : epee::http_server_impl_base<http_server>(),
+ dummy_size(payload_size)
+ {}
+
+ CHAIN_HTTP_TO_MAP2(connection_context); //forward http requests to uri map
+
+ BEGIN_URI_MAP2()
+ MAP_URI_AUTO_BIN2("/dummy", on_dummy, dummy)
+ END_URI_MAP2()
+
+ bool on_dummy(const dummy::request&, dummy::response& res, const connection_context *ctx = NULL)
+ {
+ res.payload.resize(dummy_size.load(), 'f');
+ return true;
+ }
+
+ std::atomic<std::size_t> dummy_size;
+ };
+} // anonymous
+
+TEST(http_server, response_soft_limit)
+{
+ namespace http = boost::beast::http;
+
+ http_server server{};
+ server.init(nullptr, "8080");
+ server.run(1, false);
+
+ boost::system::error_code error{};
+ boost::asio::io_context context{};
+ boost::asio::ip::tcp::socket stream{context};
+ stream.connect(
+ boost::asio::ip::tcp::endpoint{
+ boost::asio::ip::make_address("127.0.0.1"), 8080
+ },
+ error
+ );
+ EXPECT_FALSE(bool(error));
+
+ http::request<http::string_body> req{http::verb::get, "/dummy", 11};
+ req.set(http::field::host, "127.0.0.1");
+ req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
+ req.body() = make_payload();
+ req.prepare_payload();
+ http::write(stream, req, error);
+ EXPECT_FALSE(bool(error));
+
+ {
+ dummy::response payload{};
+ boost::beast::flat_buffer buffer;
+ http::response<http::basic_string_body<char>> res;
+ http::read(stream, buffer, res, error);
+ EXPECT_FALSE(bool(error));
+ EXPECT_EQ(200u, res.result_int());
+ EXPECT_TRUE(epee::serialization::load_t_from_binary(payload, res.body()));
+ EXPECT_EQ(payload_size, std::count(payload.payload.begin(), payload.payload.end(), 'f'));
+ }
+
+ while (!error)
+ http::write(stream, req, error);
+ server.send_stop_signal();
+}
+
+TEST(http_server, private_ip_limit)
+{
+ namespace http = boost::beast::http;
+
+ http_server server{};
+ server.dummy_size = 1;
+ server.init(nullptr, "8080");
+ server.run(1, false);
+
+ boost::system::error_code error{};
+ boost::asio::io_context context{};
+
+ http::request<http::string_body> req{http::verb::get, "/dummy", 11};
+ req.set(http::field::host, "127.0.0.1");
+ req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
+ req.body() = make_payload();
+ req.prepare_payload();
+
+ std::vector<boost::asio::ip::tcp::socket> streams{};
+ for (std::size_t i = 0; i < max_private_ips; ++i)
+ {
+ streams.emplace_back(context);
+ streams.back().connect(
+ boost::asio::ip::tcp::endpoint{
+ boost::asio::ip::make_address("127.0.0.1"), 8080
+ },
+ error
+ );
+ http::write(streams.back(), req, error);
+ EXPECT_FALSE(bool(error));
+
+ dummy::response payload{};
+ boost::beast::flat_buffer buffer;
+ http::response<http::basic_string_body<char>> res;
+
+ http::read(streams.back(), buffer, res, error);
+ EXPECT_FALSE(bool(error));
+ }
+
+ boost::asio::ip::tcp::socket stream{context};
+ stream.connect(
+ boost::asio::ip::tcp::endpoint{
+ boost::asio::ip::make_address("127.0.0.1"), 8080
+ },
+ error
+ );
+ bool failed = bool(error);
+ http::write(stream, req, error);
+ failed |= bool(error);
+ {
+ dummy::response payload{};
+ boost::beast::flat_buffer buffer;
+ http::response<http::basic_string_body<char>> res;
+
+ // make sure server ran async_accept code
+ http::read(stream, buffer, res, error);
+ }
+ failed |= bool(error);
+ EXPECT_TRUE(failed);
+}