diff options
author | Vasil Dimov <vd@FreeBSD.org> | 2021-03-11 16:27:22 +0100 |
---|---|---|
committer | Vasil Dimov <vd@FreeBSD.org> | 2021-03-16 14:58:38 +0100 |
commit | 40316a37cb02cf8a9a8b2cbd4d7153ffa57e7ec5 (patch) | |
tree | 19db70f0951663b02a31139f8afe47cc1d2c7f0f | |
parent | 2d8ac779708322e1235e823edfc9c8f6e2dd65e4 (diff) |
test: add I2P test for a runaway SAM proxy
Add a regression test for https://github.com/bitcoin/bitcoin/pull/21407.
The test creates a socket that, upon read, returns some data, but never
the expected terminator `\n`, injects that socket into the I2P code and
expects `i2p::sam::Session::Connect()` to fail, printing a specific
error message to the log.
-rw-r--r-- | src/Makefile.test.include | 1 | ||||
-rw-r--r-- | src/test/i2p_tests.cpp | 44 | ||||
-rw-r--r-- | src/test/util/net.h | 69 |
3 files changed, 114 insertions, 0 deletions
diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 6f461a989b..cf7f65e177 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -90,6 +90,7 @@ BITCOIN_TESTS =\ test/fs_tests.cpp \ test/getarg_tests.cpp \ test/hash_tests.cpp \ + test/i2p_tests.cpp \ test/interfaces_tests.cpp \ test/key_io_tests.cpp \ test/key_tests.cpp \ diff --git a/src/test/i2p_tests.cpp b/src/test/i2p_tests.cpp new file mode 100644 index 0000000000..334f71106c --- /dev/null +++ b/src/test/i2p_tests.cpp @@ -0,0 +1,44 @@ +// Copyright (c) 2021-2021 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <i2p.h> +#include <netaddress.h> +#include <test/util/logging.h> +#include <test/util/net.h> +#include <test/util/setup_common.h> +#include <threadinterrupt.h> +#include <util/system.h> + +#include <boost/test/unit_test.hpp> + +#include <memory> +#include <string> + +BOOST_FIXTURE_TEST_SUITE(i2p_tests, BasicTestingSetup) + +BOOST_AUTO_TEST_CASE(unlimited_recv) +{ + auto CreateSockOrig = CreateSock; + + // Mock CreateSock() to create MockSock. + CreateSock = [](const CService&) { + return std::make_unique<StaticContentsSock>(std::string(i2p::sam::MAX_MSG_SIZE + 1, 'a')); + }; + + CThreadInterrupt interrupt; + i2p::sam::Session session(GetDataDir() / "test_i2p_private_key", CService{}, &interrupt); + + { + ASSERT_DEBUG_LOG("Creating SAM session"); + ASSERT_DEBUG_LOG("too many bytes without a terminator"); + + i2p::Connection conn; + bool proxy_error; + BOOST_REQUIRE(!session.Connect(CService{}, conn, proxy_error)); + } + + CreateSock = CreateSockOrig; +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/util/net.h b/src/test/util/net.h index e25036be26..2b7988413f 100644 --- a/src/test/util/net.h +++ b/src/test/util/net.h @@ -5,7 +5,13 @@ #ifndef BITCOIN_TEST_UTIL_NET_H #define BITCOIN_TEST_UTIL_NET_H +#include <compat.h> #include <net.h> +#include <util/sock.h> + +#include <cassert> +#include <cstring> +#include <string> struct ConnmanTestMsg : public CConnman { using CConnman::CConnman; @@ -61,4 +67,67 @@ constexpr ConnectionType ALL_CONNECTION_TYPES[]{ ConnectionType::ADDR_FETCH, }; +/** + * A mocked Sock alternative that returns a statically contained data upon read and succeeds + * and ignores all writes. The data to be returned is given to the constructor and when it is + * exhausted an EOF is returned by further reads. + */ +class StaticContentsSock : public Sock +{ +public: + explicit StaticContentsSock(const std::string& contents) : m_contents{contents}, m_consumed{0} + { + // Just a dummy number that is not INVALID_SOCKET. + static_assert(INVALID_SOCKET != 1000); + m_socket = 1000; + } + + ~StaticContentsSock() override { Reset(); } + + StaticContentsSock& operator=(Sock&& other) override + { + assert(false && "Move of Sock into MockSock not allowed."); + return *this; + } + + void Reset() override + { + m_socket = INVALID_SOCKET; + } + + ssize_t Send(const void*, size_t len, int) const override { return len; } + + ssize_t Recv(void* buf, size_t len, int flags) const override + { + const size_t consume_bytes{std::min(len, m_contents.size() - m_consumed)}; + std::memcpy(buf, m_contents.data() + m_consumed, consume_bytes); + if ((flags & MSG_PEEK) == 0) { + m_consumed += consume_bytes; + } + return consume_bytes; + } + + int Connect(const sockaddr*, socklen_t) const override { return 0; } + + int GetSockOpt(int level, int opt_name, void* opt_val, socklen_t* opt_len) const override + { + std::memset(opt_val, 0x0, *opt_len); + return 0; + } + + bool Wait(std::chrono::milliseconds timeout, + Event requested, + Event* occurred = nullptr) const override + { + if (occurred != nullptr) { + *occurred = requested; + } + return true; + } + +private: + const std::string m_contents; + mutable size_t m_consumed; +}; + #endif // BITCOIN_TEST_UTIL_NET_H |