Skip to content

Commit 40316a3

Browse files
committed
test: add I2P test for a runaway SAM proxy
Add a regression test for bitcoin/bitcoin#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.
1 parent 2d8ac77 commit 40316a3

File tree

3 files changed

+114
-0
lines changed

3 files changed

+114
-0
lines changed

src/Makefile.test.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ BITCOIN_TESTS =\
9090
test/fs_tests.cpp \
9191
test/getarg_tests.cpp \
9292
test/hash_tests.cpp \
93+
test/i2p_tests.cpp \
9394
test/interfaces_tests.cpp \
9495
test/key_io_tests.cpp \
9596
test/key_tests.cpp \

src/test/i2p_tests.cpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright (c) 2021-2021 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <i2p.h>
6+
#include <netaddress.h>
7+
#include <test/util/logging.h>
8+
#include <test/util/net.h>
9+
#include <test/util/setup_common.h>
10+
#include <threadinterrupt.h>
11+
#include <util/system.h>
12+
13+
#include <boost/test/unit_test.hpp>
14+
15+
#include <memory>
16+
#include <string>
17+
18+
BOOST_FIXTURE_TEST_SUITE(i2p_tests, BasicTestingSetup)
19+
20+
BOOST_AUTO_TEST_CASE(unlimited_recv)
21+
{
22+
auto CreateSockOrig = CreateSock;
23+
24+
// Mock CreateSock() to create MockSock.
25+
CreateSock = [](const CService&) {
26+
return std::make_unique<StaticContentsSock>(std::string(i2p::sam::MAX_MSG_SIZE + 1, 'a'));
27+
};
28+
29+
CThreadInterrupt interrupt;
30+
i2p::sam::Session session(GetDataDir() / "test_i2p_private_key", CService{}, &interrupt);
31+
32+
{
33+
ASSERT_DEBUG_LOG("Creating SAM session");
34+
ASSERT_DEBUG_LOG("too many bytes without a terminator");
35+
36+
i2p::Connection conn;
37+
bool proxy_error;
38+
BOOST_REQUIRE(!session.Connect(CService{}, conn, proxy_error));
39+
}
40+
41+
CreateSock = CreateSockOrig;
42+
}
43+
44+
BOOST_AUTO_TEST_SUITE_END()

src/test/util/net.h

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@
55
#ifndef BITCOIN_TEST_UTIL_NET_H
66
#define BITCOIN_TEST_UTIL_NET_H
77

8+
#include <compat.h>
89
#include <net.h>
10+
#include <util/sock.h>
11+
12+
#include <cassert>
13+
#include <cstring>
14+
#include <string>
915

1016
struct ConnmanTestMsg : public CConnman {
1117
using CConnman::CConnman;
@@ -61,4 +67,67 @@ constexpr ConnectionType ALL_CONNECTION_TYPES[]{
6167
ConnectionType::ADDR_FETCH,
6268
};
6369

70+
/**
71+
* A mocked Sock alternative that returns a statically contained data upon read and succeeds
72+
* and ignores all writes. The data to be returned is given to the constructor and when it is
73+
* exhausted an EOF is returned by further reads.
74+
*/
75+
class StaticContentsSock : public Sock
76+
{
77+
public:
78+
explicit StaticContentsSock(const std::string& contents) : m_contents{contents}, m_consumed{0}
79+
{
80+
// Just a dummy number that is not INVALID_SOCKET.
81+
static_assert(INVALID_SOCKET != 1000);
82+
m_socket = 1000;
83+
}
84+
85+
~StaticContentsSock() override { Reset(); }
86+
87+
StaticContentsSock& operator=(Sock&& other) override
88+
{
89+
assert(false && "Move of Sock into MockSock not allowed.");
90+
return *this;
91+
}
92+
93+
void Reset() override
94+
{
95+
m_socket = INVALID_SOCKET;
96+
}
97+
98+
ssize_t Send(const void*, size_t len, int) const override { return len; }
99+
100+
ssize_t Recv(void* buf, size_t len, int flags) const override
101+
{
102+
const size_t consume_bytes{std::min(len, m_contents.size() - m_consumed)};
103+
std::memcpy(buf, m_contents.data() + m_consumed, consume_bytes);
104+
if ((flags & MSG_PEEK) == 0) {
105+
m_consumed += consume_bytes;
106+
}
107+
return consume_bytes;
108+
}
109+
110+
int Connect(const sockaddr*, socklen_t) const override { return 0; }
111+
112+
int GetSockOpt(int level, int opt_name, void* opt_val, socklen_t* opt_len) const override
113+
{
114+
std::memset(opt_val, 0x0, *opt_len);
115+
return 0;
116+
}
117+
118+
bool Wait(std::chrono::milliseconds timeout,
119+
Event requested,
120+
Event* occurred = nullptr) const override
121+
{
122+
if (occurred != nullptr) {
123+
*occurred = requested;
124+
}
125+
return true;
126+
}
127+
128+
private:
129+
const std::string m_contents;
130+
mutable size_t m_consumed;
131+
};
132+
64133
#endif // BITCOIN_TEST_UTIL_NET_H

0 commit comments

Comments
 (0)