Skip to content

Commit f9e86d8

Browse files
committed
Merge #21387: p2p: Refactor sock to add I2P fuzz and unit tests
40316a3 test: add I2P test for a runaway SAM proxy (Vasil Dimov) 2d8ac77 fuzz: add tests for the I2P Session public interface (Vasil Dimov) 9947e44 i2p: use pointers to Sock to accommodate mocking (Vasil Dimov) 82d360b net: change ConnectSocketDirectly() to take a Sock argument (Vasil Dimov) b586110 net: add connect() and getsockopt() wrappers to Sock (Vasil Dimov) 5a887d4 fuzz: avoid FuzzedSock::Recv() repeated errors with EAGAIN (Vasil Dimov) 3088f83 fuzz: extend FuzzedSock::Recv() to support MSG_PEEK (Vasil Dimov) 9b05c49 fuzz: implement unimplemented FuzzedSock methods (Vasil Dimov) Pull request description: Change the networking code and the I2P code to be fully mockable and use `FuzzedSocket` to fuzz the I2P methods `Listen()`, `Accept()` and `Connect()`. Add a mocked `Sock` implementation that returns a predefined data on reads and use it for a regression unit test for the bug fixed in bitcoin/bitcoin#21407. ACKs for top commit: practicalswift: Tested ACK 40316a3 MarcoFalke: Concept ACK 40316a3 jonatack: re-ACK 40316a3 reviewed `git range-diff 01bb3af 23c861d 40316a3` and the new unit test commit, debug built, ran unit tests, ran bitcoind with an I2P service and network operation with seven I2P peers (2 in, 5 out) is looking nominal laanwj: Code review ACK 40316a3 Tree-SHA512: 7fc4f129849e16e0c7e16662d9f4d35dfcc369bb31450ee369a2b97bdca95285533bee7787983e881e5a3d248f912afb42b4a2299d5860ace7129b0b19623cc8
2 parents dede9eb + 40316a3 commit f9e86d8

File tree

12 files changed

+344
-87
lines changed

12 files changed

+344
-87
lines changed

src/Makefile.test.include

Lines changed: 2 additions & 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 \
@@ -240,6 +241,7 @@ test_fuzz_fuzz_SOURCES = \
240241
test/fuzz/golomb_rice.cpp \
241242
test/fuzz/hex.cpp \
242243
test/fuzz/http_request.cpp \
244+
test/fuzz/i2p.cpp \
243245
test/fuzz/integer.cpp \
244246
test/fuzz/key.cpp \
245247
test/fuzz/key_io.cpp \

src/i2p.cpp

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <util/system.h>
2121

2222
#include <chrono>
23+
#include <memory>
2324
#include <stdexcept>
2425
#include <string>
2526

@@ -115,7 +116,8 @@ namespace sam {
115116
Session::Session(const fs::path& private_key_file,
116117
const CService& control_host,
117118
CThreadInterrupt* interrupt)
118-
: m_private_key_file(private_key_file), m_control_host(control_host), m_interrupt(interrupt)
119+
: m_private_key_file(private_key_file), m_control_host(control_host), m_interrupt(interrupt),
120+
m_control_sock(std::make_unique<Sock>(INVALID_SOCKET))
119121
{
120122
}
121123

@@ -145,15 +147,15 @@ bool Session::Accept(Connection& conn)
145147
try {
146148
while (!*m_interrupt) {
147149
Sock::Event occurred;
148-
conn.sock.Wait(MAX_WAIT_FOR_IO, Sock::RECV, &occurred);
150+
conn.sock->Wait(MAX_WAIT_FOR_IO, Sock::RECV, &occurred);
149151

150152
if ((occurred & Sock::RECV) == 0) {
151153
// Timeout, no incoming connections within MAX_WAIT_FOR_IO.
152154
continue;
153155
}
154156

155157
const std::string& peer_dest =
156-
conn.sock.RecvUntilTerminator('\n', MAX_WAIT_FOR_IO, *m_interrupt, MAX_MSG_SIZE);
158+
conn.sock->RecvUntilTerminator('\n', MAX_WAIT_FOR_IO, *m_interrupt, MAX_MSG_SIZE);
157159

158160
conn.peer = CService(DestB64ToAddr(peer_dest), Params().GetDefaultPort());
159161

@@ -171,7 +173,7 @@ bool Session::Connect(const CService& to, Connection& conn, bool& proxy_error)
171173
proxy_error = true;
172174

173175
std::string session_id;
174-
Sock sock;
176+
std::unique_ptr<Sock> sock;
175177
conn.peer = to;
176178

177179
try {
@@ -184,12 +186,12 @@ bool Session::Connect(const CService& to, Connection& conn, bool& proxy_error)
184186
}
185187

186188
const Reply& lookup_reply =
187-
SendRequestAndGetReply(sock, strprintf("NAMING LOOKUP NAME=%s", to.ToStringIP()));
189+
SendRequestAndGetReply(*sock, strprintf("NAMING LOOKUP NAME=%s", to.ToStringIP()));
188190

189191
const std::string& dest = lookup_reply.Get("VALUE");
190192

191193
const Reply& connect_reply = SendRequestAndGetReply(
192-
sock, strprintf("STREAM CONNECT ID=%s DESTINATION=%s SILENT=false", session_id, dest),
194+
*sock, strprintf("STREAM CONNECT ID=%s DESTINATION=%s SILENT=false", session_id, dest),
193195
false);
194196

195197
const std::string& result = connect_reply.Get("RESULT");
@@ -271,29 +273,29 @@ Session::Reply Session::SendRequestAndGetReply(const Sock& sock,
271273
return reply;
272274
}
273275

274-
Sock Session::Hello() const
276+
std::unique_ptr<Sock> Session::Hello() const
275277
{
276278
auto sock = CreateSock(m_control_host);
277279

278280
if (!sock) {
279281
throw std::runtime_error("Cannot create socket");
280282
}
281283

282-
if (!ConnectSocketDirectly(m_control_host, sock->Get(), nConnectTimeout, true)) {
284+
if (!ConnectSocketDirectly(m_control_host, *sock, nConnectTimeout, true)) {
283285
throw std::runtime_error(strprintf("Cannot connect to %s", m_control_host.ToString()));
284286
}
285287

286288
SendRequestAndGetReply(*sock, "HELLO VERSION MIN=3.1 MAX=3.1");
287289

288-
return std::move(*sock);
290+
return sock;
289291
}
290292

291293
void Session::CheckControlSock()
292294
{
293295
LOCK(m_mutex);
294296

295297
std::string errmsg;
296-
if (!m_control_sock.IsConnected(errmsg)) {
298+
if (!m_control_sock->IsConnected(errmsg)) {
297299
Log("Control socket error: %s", errmsg);
298300
Disconnect();
299301
}
@@ -341,26 +343,26 @@ Binary Session::MyDestination() const
341343
void Session::CreateIfNotCreatedAlready()
342344
{
343345
std::string errmsg;
344-
if (m_control_sock.IsConnected(errmsg)) {
346+
if (m_control_sock->IsConnected(errmsg)) {
345347
return;
346348
}
347349

348350
Log("Creating SAM session with %s", m_control_host.ToString());
349351

350-
Sock sock = Hello();
352+
auto sock = Hello();
351353

352354
const auto& [read_ok, data] = ReadBinaryFile(m_private_key_file);
353355
if (read_ok) {
354356
m_private_key.assign(data.begin(), data.end());
355357
} else {
356-
GenerateAndSavePrivateKey(sock);
358+
GenerateAndSavePrivateKey(*sock);
357359
}
358360

359361
const std::string& session_id = GetRandHash().GetHex().substr(0, 10); // full is an overkill, too verbose in the logs
360362
const std::string& private_key_b64 = SwapBase64(EncodeBase64(m_private_key));
361363

362-
SendRequestAndGetReply(sock, strprintf("SESSION CREATE STYLE=STREAM ID=%s DESTINATION=%s",
363-
session_id, private_key_b64));
364+
SendRequestAndGetReply(*sock, strprintf("SESSION CREATE STYLE=STREAM ID=%s DESTINATION=%s",
365+
session_id, private_key_b64));
364366

365367
m_my_addr = CService(DestBinToAddr(MyDestination()), Params().GetDefaultPort());
366368
m_session_id = session_id;
@@ -370,12 +372,12 @@ void Session::CreateIfNotCreatedAlready()
370372
m_my_addr.ToString());
371373
}
372374

373-
Sock Session::StreamAccept()
375+
std::unique_ptr<Sock> Session::StreamAccept()
374376
{
375-
Sock sock = Hello();
377+
auto sock = Hello();
376378

377379
const Reply& reply = SendRequestAndGetReply(
378-
sock, strprintf("STREAM ACCEPT ID=%s SILENT=false", m_session_id), false);
380+
*sock, strprintf("STREAM ACCEPT ID=%s SILENT=false", m_session_id), false);
379381

380382
const std::string& result = reply.Get("RESULT");
381383

@@ -393,14 +395,14 @@ Sock Session::StreamAccept()
393395

394396
void Session::Disconnect()
395397
{
396-
if (m_control_sock.Get() != INVALID_SOCKET) {
398+
if (m_control_sock->Get() != INVALID_SOCKET) {
397399
if (m_session_id.empty()) {
398400
Log("Destroying incomplete session");
399401
} else {
400402
Log("Destroying session %s", m_session_id);
401403
}
402404
}
403-
m_control_sock.Reset();
405+
m_control_sock->Reset();
404406
m_session_id.clear();
405407
}
406408
} // namespace sam

src/i2p.h

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <threadinterrupt.h>
1313
#include <util/sock.h>
1414

15+
#include <memory>
1516
#include <optional>
1617
#include <string>
1718
#include <unordered_map>
@@ -29,7 +30,7 @@ using Binary = std::vector<uint8_t>;
2930
*/
3031
struct Connection {
3132
/** Connected socket. */
32-
Sock sock;
33+
std::unique_ptr<Sock> sock;
3334

3435
/** Our I2P address. */
3536
CService me;
@@ -166,7 +167,7 @@ class Session
166167
* @return a connected socket
167168
* @throws std::runtime_error if an error occurs
168169
*/
169-
Sock Hello() const EXCLUSIVE_LOCKS_REQUIRED(m_mutex);
170+
std::unique_ptr<Sock> Hello() const EXCLUSIVE_LOCKS_REQUIRED(m_mutex);
170171

171172
/**
172173
* Check the control socket for errors and possibly disconnect.
@@ -204,10 +205,11 @@ class Session
204205

205206
/**
206207
* Open a new connection to the SAM proxy and issue "STREAM ACCEPT" request using the existing
207-
* session id. Return the idle socket that is waiting for a peer to connect to us.
208+
* session id.
209+
* @return the idle socket that is waiting for a peer to connect to us
208210
* @throws std::runtime_error if an error occurs
209211
*/
210-
Sock StreamAccept() EXCLUSIVE_LOCKS_REQUIRED(m_mutex);
212+
std::unique_ptr<Sock> StreamAccept() EXCLUSIVE_LOCKS_REQUIRED(m_mutex);
211213

212214
/**
213215
* Destroy the session, closing the internally used sockets.
@@ -248,7 +250,7 @@ class Session
248250
* connections and make outgoing ones.
249251
* See https://geti2p.net/en/docs/api/samv3
250252
*/
251-
Sock m_control_sock GUARDED_BY(m_mutex);
253+
std::unique_ptr<Sock> m_control_sock GUARDED_BY(m_mutex);
252254

253255
/**
254256
* Our .b32.i2p address.

src/net.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,7 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo
432432
i2p::Connection conn;
433433
if (m_i2p_sam_session->Connect(addrConnect, conn, proxyConnectionFailed)) {
434434
connected = true;
435-
sock = std::make_unique<Sock>(std::move(conn.sock));
435+
sock = std::move(conn.sock);
436436
addr_bind = CAddress{conn.me, NODE_NONE};
437437
}
438438
} else if (GetProxy(addrConnect.GetNetwork(), proxy)) {
@@ -448,7 +448,7 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo
448448
if (!sock) {
449449
return nullptr;
450450
}
451-
connected = ConnectSocketDirectly(addrConnect, sock->Get(), nConnectTimeout,
451+
connected = ConnectSocketDirectly(addrConnect, *sock, nConnectTimeout,
452452
conn_type == ConnectionType::MANUAL);
453453
}
454454
if (!proxyConnectionFailed) {
@@ -2253,7 +2253,7 @@ void CConnman::ThreadI2PAcceptIncoming()
22532253
continue;
22542254
}
22552255

2256-
CreateNodeFromAcceptedSocket(conn.sock.Release(), NetPermissionFlags::PF_NONE,
2256+
CreateNodeFromAcceptedSocket(conn.sock->Release(), NetPermissionFlags::PF_NONE,
22572257
CAddress{conn.me, NODE_NONE}, CAddress{conn.peer, NODE_NONE});
22582258
}
22592259
}

src/netbase.cpp

Lines changed: 23 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -537,12 +537,12 @@ static void LogConnectFailure(bool manual_connection, const char* fmt, const Arg
537537
}
538538
}
539539

540-
bool ConnectSocketDirectly(const CService &addrConnect, const SOCKET& hSocket, int nTimeout, bool manual_connection)
540+
bool ConnectSocketDirectly(const CService &addrConnect, const Sock& sock, int nTimeout, bool manual_connection)
541541
{
542542
// Create a sockaddr from the specified service.
543543
struct sockaddr_storage sockaddr;
544544
socklen_t len = sizeof(sockaddr);
545-
if (hSocket == INVALID_SOCKET) {
545+
if (sock.Get() == INVALID_SOCKET) {
546546
LogPrintf("Cannot connect to %s: invalid socket\n", addrConnect.ToString());
547547
return false;
548548
}
@@ -552,55 +552,42 @@ bool ConnectSocketDirectly(const CService &addrConnect, const SOCKET& hSocket, i
552552
}
553553

554554
// Connect to the addrConnect service on the hSocket socket.
555-
if (connect(hSocket, (struct sockaddr*)&sockaddr, len) == SOCKET_ERROR)
556-
{
555+
if (sock.Connect(reinterpret_cast<struct sockaddr*>(&sockaddr), len) == SOCKET_ERROR) {
557556
int nErr = WSAGetLastError();
558557
// WSAEINVAL is here because some legacy version of winsock uses it
559558
if (nErr == WSAEINPROGRESS || nErr == WSAEWOULDBLOCK || nErr == WSAEINVAL)
560559
{
561560
// Connection didn't actually fail, but is being established
562561
// asynchronously. Thus, use async I/O api (select/poll)
563562
// synchronously to check for successful connection with a timeout.
564-
#ifdef USE_POLL
565-
struct pollfd pollfd = {};
566-
pollfd.fd = hSocket;
567-
pollfd.events = POLLIN | POLLOUT;
568-
int nRet = poll(&pollfd, 1, nTimeout);
569-
#else
570-
struct timeval timeout = MillisToTimeval(nTimeout);
571-
fd_set fdset;
572-
FD_ZERO(&fdset);
573-
FD_SET(hSocket, &fdset);
574-
int nRet = select(hSocket + 1, nullptr, &fdset, nullptr, &timeout);
575-
#endif
576-
// Upon successful completion, both select and poll return the total
577-
// number of file descriptors that have been selected. A value of 0
578-
// indicates that the call timed out and no file descriptors have
579-
// been selected.
580-
if (nRet == 0)
581-
{
582-
LogPrint(BCLog::NET, "connection to %s timeout\n", addrConnect.ToString());
563+
const Sock::Event requested = Sock::RECV | Sock::SEND;
564+
Sock::Event occurred;
565+
if (!sock.Wait(std::chrono::milliseconds{nTimeout}, requested, &occurred)) {
566+
LogPrintf("wait for connect to %s failed: %s\n",
567+
addrConnect.ToString(),
568+
NetworkErrorString(WSAGetLastError()));
583569
return false;
584-
}
585-
if (nRet == SOCKET_ERROR)
586-
{
587-
LogPrintf("select() for %s failed: %s\n", addrConnect.ToString(), NetworkErrorString(WSAGetLastError()));
570+
} else if (occurred == 0) {
571+
LogPrint(BCLog::NET, "connection attempt to %s timed out\n", addrConnect.ToString());
588572
return false;
589573
}
590574

591-
// Even if the select/poll was successful, the connect might not
575+
// Even if the wait was successful, the connect might not
592576
// have been successful. The reason for this failure is hidden away
593577
// in the SO_ERROR for the socket in modern systems. We read it into
594-
// nRet here.
595-
socklen_t nRetSize = sizeof(nRet);
596-
if (getsockopt(hSocket, SOL_SOCKET, SO_ERROR, (sockopt_arg_type)&nRet, &nRetSize) == SOCKET_ERROR)
597-
{
578+
// sockerr here.
579+
int sockerr;
580+
socklen_t sockerr_len = sizeof(sockerr);
581+
if (sock.GetSockOpt(SOL_SOCKET, SO_ERROR, (sockopt_arg_type)&sockerr, &sockerr_len) ==
582+
SOCKET_ERROR) {
598583
LogPrintf("getsockopt() for %s failed: %s\n", addrConnect.ToString(), NetworkErrorString(WSAGetLastError()));
599584
return false;
600585
}
601-
if (nRet != 0)
602-
{
603-
LogConnectFailure(manual_connection, "connect() to %s failed after select(): %s", addrConnect.ToString(), NetworkErrorString(nRet));
586+
if (sockerr != 0) {
587+
LogConnectFailure(manual_connection,
588+
"connect() to %s failed after wait: %s",
589+
addrConnect.ToString(),
590+
NetworkErrorString(sockerr));
604591
return false;
605592
}
606593
}
@@ -668,7 +655,7 @@ bool IsProxy(const CNetAddr &addr) {
668655
bool ConnectThroughProxy(const proxyType& proxy, const std::string& strDest, uint16_t port, const Sock& sock, int nTimeout, bool& outProxyConnectionFailed)
669656
{
670657
// first connect to proxy server
671-
if (!ConnectSocketDirectly(proxy.proxy, sock.Get(), nTimeout, true)) {
658+
if (!ConnectSocketDirectly(proxy.proxy, sock, nTimeout, true)) {
672659
outProxyConnectionFailed = true;
673660
return false;
674661
}

src/netbase.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,15 +194,15 @@ extern std::function<std::unique_ptr<Sock>(const CService&)> CreateSock;
194194
* Try to connect to the specified service on the specified socket.
195195
*
196196
* @param addrConnect The service to which to connect.
197-
* @param hSocket The socket on which to connect.
197+
* @param sock The socket on which to connect.
198198
* @param nTimeout Wait this many milliseconds for the connection to be
199199
* established.
200200
* @param manual_connection Whether or not the connection was manually requested
201201
* (e.g. through the addnode RPC)
202202
*
203203
* @returns Whether or not a connection was successfully made.
204204
*/
205-
bool ConnectSocketDirectly(const CService &addrConnect, const SOCKET& hSocket, int nTimeout, bool manual_connection);
205+
bool ConnectSocketDirectly(const CService &addrConnect, const Sock& sock, int nTimeout, bool manual_connection);
206206

207207
/**
208208
* Connect to a specified destination service through a SOCKS5 proxy by first

0 commit comments

Comments
 (0)