aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVasil Dimov <vd@FreeBSD.org>2021-03-11 16:27:22 +0100
committerVasil Dimov <vd@FreeBSD.org>2021-03-16 14:58:38 +0100
commit40316a37cb02cf8a9a8b2cbd4d7153ffa57e7ec5 (patch)
tree19db70f0951663b02a31139f8afe47cc1d2c7f0f
parent2d8ac779708322e1235e823edfc9c8f6e2dd65e4 (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.include1
-rw-r--r--src/test/i2p_tests.cpp44
-rw-r--r--src/test/util/net.h69
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