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.
pull/826/head
Vasil Dimov 4 years ago
parent 2d8ac77970
commit 40316a37cb
No known key found for this signature in database
GPG Key ID: 54DF06F64B55CBBF

@ -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 \

@ -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()

@ -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

Loading…
Cancel
Save