Skip to content

Commit e4b22a6

Browse files
Merge dashpay#6054: backport: merge bitcoin#21879, bitcoin#23604, bitcoin#24357, bitcoin#25426, bitcoin#24378 (sockets backports)
c24804c merge bitcoin#24378: make bind() and listen() mockable/testable (Kittywhiskers Van Gogh) be19868 merge bitcoin#25426: add new method Sock::GetSockName() that wraps getsockname() and use it in GetBindAddress() (Kittywhiskers Van Gogh) 6b159f1 merge bitcoin#24357: make setsockopt() and SetSocketNoDelay() mockable/testable (Kittywhiskers Van Gogh) 9c751ef merge bitcoin#23604: Use Sock in CNode (Kittywhiskers Van Gogh) 508044c merge bitcoin#21879: wrap accept() and extend usage of Sock (Kittywhiskers Van Gogh) Pull request description: ## Additional Information * Dependency for dashpay#6018 ## Breaking Changes None expected. ## Checklist: - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas **(note: N/A)** - [x] I have added or updated relevant unit/integration/functional/e2e tests - [x] I have made corresponding changes to the documentation **(note: N/A)** - [x] I have assigned this pull request to a milestone _(for repository code-owners and collaborators only)_ ACKs for top commit: PastaPastaPasta: utACK c24804c Tree-SHA512: 5149de0f1983bb56517c30b31d137b33b8a49b0e695be2dada71ff3e3bb22908556db343391b7df7e3c7c2ed60ae1fc11a4f4af4f47e35a2a1d3ce7463c03d41
2 parents 2f93ee4 + c24804c commit e4b22a6

File tree

11 files changed

+505
-172
lines changed

11 files changed

+505
-172
lines changed

src/net.cpp

Lines changed: 102 additions & 74 deletions
Large diffs are not rendered by default.

src/net.h

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include <uint256.h>
3030
#include <util/check.h>
3131
#include <util/edge.h>
32+
#include <util/sock.h>
3233
#include <util/system.h>
3334
#include <util/wpipe.h>
3435
#include <consensus/params.h>
@@ -439,7 +440,17 @@ class CNode
439440

440441
NetPermissionFlags m_permissionFlags{ NetPermissionFlags::None };
441442
std::atomic<ServiceFlags> nServices{NODE_NONE};
442-
SOCKET hSocket GUARDED_BY(cs_hSocket);
443+
444+
/**
445+
* Socket used for communication with the node.
446+
* May not own a Sock object (after `CloseSocketDisconnect()` or during tests).
447+
* `shared_ptr` (instead of `unique_ptr`) is used to avoid premature close of
448+
* the underlying file descriptor by one thread while another thread is
449+
* poll(2)-ing it for activity.
450+
* @see https://github.com/bitcoin/bitcoin/issues/21744 for details.
451+
*/
452+
std::shared_ptr<Sock> m_sock GUARDED_BY(m_sock_mutex);
453+
443454
/** Total size of all vSendMsg entries */
444455
size_t nSendSize GUARDED_BY(cs_vSend){0};
445456
/** Offset inside the first vSendMsg already sent */
@@ -448,7 +459,7 @@ class CNode
448459
std::list<std::vector<unsigned char>> vSendMsg GUARDED_BY(cs_vSend);
449460
std::atomic<size_t> nSendMsgSize{0};
450461
Mutex cs_vSend;
451-
Mutex cs_hSocket;
462+
Mutex m_sock_mutex;
452463
Mutex cs_vRecv;
453464

454465
RecursiveMutex cs_vProcessMsg;
@@ -629,8 +640,7 @@ class CNode
629640

630641
bool IsBlockRelayOnly() const;
631642

632-
CNode(NodeId id, ServiceFlags nLocalServicesIn, SOCKET hSocketIn, const CAddress &addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const CAddress &addrBindIn, const std::string &addrNameIn, ConnectionType conn_type_in, bool inbound_onion, std::unique_ptr<i2p::sam::Session>&& i2p_sam_session = nullptr);
633-
~CNode();
643+
CNode(NodeId id, ServiceFlags nLocalServicesIn, std::shared_ptr<Sock> sock, const CAddress &addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const CAddress &addrBindIn, const std::string &addrNameIn, ConnectionType conn_type_in, bool inbound_onion, std::unique_ptr<i2p::sam::Session>&& i2p_sam_session = nullptr);
634644
CNode(const CNode&) = delete;
635645
CNode& operator=(const CNode&) = delete;
636646

@@ -684,7 +694,7 @@ class CNode
684694
nRefCount--;
685695
}
686696

687-
void CloseSocketDisconnect(CConnman* connman) EXCLUSIVE_LOCKS_REQUIRED(!cs_hSocket);
697+
void CloseSocketDisconnect(CConnman* connman) EXCLUSIVE_LOCKS_REQUIRED(!m_sock_mutex);
688698

689699
void CopyStats(CNodeStats& stats) EXCLUSIVE_LOCKS_REQUIRED(!m_subver_mutex, !m_addr_local_mutex, !cs_vSend, !cs_vRecv);
690700

@@ -794,7 +804,7 @@ class CNode
794804
* closed.
795805
* Otherwise this unique_ptr is empty.
796806
*/
797-
std::unique_ptr<i2p::sam::Session> m_i2p_sam_session GUARDED_BY(cs_hSocket);
807+
std::unique_ptr<i2p::sam::Session> m_i2p_sam_session GUARDED_BY(m_sock_mutex);
798808
};
799809

800810
/**
@@ -1221,9 +1231,13 @@ friend class CNode;
12211231
private:
12221232
struct ListenSocket {
12231233
public:
1224-
SOCKET socket;
1234+
std::shared_ptr<Sock> sock;
12251235
inline void AddSocketPermissionFlags(NetPermissionFlags& flags) const { NetPermissions::AddFlag(flags, m_permissions); }
1226-
ListenSocket(SOCKET socket_, NetPermissionFlags permissions_) : socket(socket_), m_permissions(permissions_) {}
1236+
ListenSocket(std::shared_ptr<Sock> sock_, NetPermissionFlags permissions_)
1237+
: sock{sock_}, m_permissions{permissions_}
1238+
{
1239+
}
1240+
12271241
private:
12281242
NetPermissionFlags m_permissions;
12291243
};
@@ -1251,12 +1265,12 @@ friend class CNode;
12511265
/**
12521266
* Create a `CNode` object from a socket that has just been accepted and add the node to
12531267
* the `m_nodes` member.
1254-
* @param[in] hSocket Connected socket to communicate with the peer.
1268+
* @param[in] sock Connected socket to communicate with the peer.
12551269
* @param[in] permissionFlags The peer's permissions.
12561270
* @param[in] addr_bind The address and port at our side of the connection.
12571271
* @param[in] addr The address and port at the peer's side of the connection.
12581272
*/
1259-
void CreateNodeFromAcceptedSocket(SOCKET hSocket,
1273+
void CreateNodeFromAcceptedSocket(std::unique_ptr<Sock>&& sock,
12601274
NetPermissionFlags permissionFlags,
12611275
const CAddress& addr_bind,
12621276
const CAddress& addr,

src/netbase.cpp

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -498,10 +498,11 @@ std::unique_ptr<Sock> CreateSockTCP(const CService& address_family)
498498
return nullptr;
499499
}
500500

501+
auto sock = std::make_unique<Sock>(hSocket);
502+
501503
// Ensure that waiting for I/O on this socket won't result in undefined
502504
// behavior.
503-
if (!IsSelectableSocket(hSocket)) {
504-
CloseSocket(hSocket);
505+
if (!IsSelectableSocket(sock->Get())) {
505506
LogPrintf("Cannot create connection: non-selectable socket created (fd >= FD_SETSIZE ?)\n");
506507
return nullptr;
507508
}
@@ -510,19 +511,24 @@ std::unique_ptr<Sock> CreateSockTCP(const CService& address_family)
510511
int set = 1;
511512
// Set the no-sigpipe option on the socket for BSD systems, other UNIXes
512513
// should use the MSG_NOSIGNAL flag for every send.
513-
setsockopt(hSocket, SOL_SOCKET, SO_NOSIGPIPE, (void*)&set, sizeof(int));
514+
if (sock->SetSockOpt(SOL_SOCKET, SO_NOSIGPIPE, (void*)&set, sizeof(int)) == SOCKET_ERROR) {
515+
LogPrintf("Error setting SO_NOSIGPIPE on socket: %s, continuing anyway\n",
516+
NetworkErrorString(WSAGetLastError()));
517+
}
514518
#endif
515519

516520
// Set the no-delay option (disable Nagle's algorithm) on the TCP socket.
517-
SetSocketNoDelay(hSocket);
521+
const int on{1};
522+
if (sock->SetSockOpt(IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)) == SOCKET_ERROR) {
523+
LogPrint(BCLog::NET, "Unable to set TCP_NODELAY on a newly created socket, continuing anyway\n");
524+
}
518525

519526
// Set the non-blocking option on the socket.
520-
if (!SetSocketNonBlocking(hSocket)) {
521-
CloseSocket(hSocket);
527+
if (!SetSocketNonBlocking(sock->Get())) {
522528
LogPrintf("Error setting socket to non-blocking: %s\n", NetworkErrorString(WSAGetLastError()));
523529
return nullptr;
524530
}
525-
return std::make_unique<Sock>(hSocket);
531+
return sock;
526532
}
527533

528534
std::function<std::unique_ptr<Sock>(const CService&)> CreateSock = CreateSockTCP;
@@ -729,13 +735,6 @@ bool SetSocketNonBlocking(const SOCKET& hSocket)
729735
return true;
730736
}
731737

732-
bool SetSocketNoDelay(const SOCKET& hSocket)
733-
{
734-
int set = 1;
735-
int rc = setsockopt(hSocket, IPPROTO_TCP, TCP_NODELAY, (const char*)&set, sizeof(int));
736-
return rc == 0;
737-
}
738-
739738
void InterruptSocks5(bool interrupt)
740739
{
741740
interruptSocks5Recv = interrupt;

src/netbase.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,8 +227,6 @@ bool ConnectThroughProxy(const Proxy& proxy, const std::string& strDest, uint16_
227227

228228
/** Enable non-blocking mode for a socket */
229229
bool SetSocketNonBlocking(const SOCKET& hSocket);
230-
/** Set the TCP_NODELAY flag on a socket */
231-
bool SetSocketNoDelay(const SOCKET& hSocket);
232230
void InterruptSocks5(bool interrupt);
233231

234232
/**

src/test/denialofservice_tests.cpp

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,16 @@ BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction)
7474

7575
// Mock an outbound peer
7676
CAddress addr1(ip(0xa0b0c001), NODE_NONE);
77-
CNode dummyNode1(id++, ServiceFlags(NODE_NETWORK), INVALID_SOCKET, addr1, /* nKeyedNetGroupIn */ 0, /* nLocalHostNonceIn */ 0, CAddress(), /* pszDest */ "", ConnectionType::OUTBOUND_FULL_RELAY, /* inbound_onion */ false);
77+
CNode dummyNode1{id++,
78+
ServiceFlags(NODE_NETWORK),
79+
/*sock=*/nullptr,
80+
addr1,
81+
/*nKeyedNetGroupIn=*/0,
82+
/*nLocalHostNonceIn=*/0,
83+
CAddress(),
84+
/*addrNameIn=*/"",
85+
ConnectionType::OUTBOUND_FULL_RELAY,
86+
/*inbound_onion=*/false};
7887
dummyNode1.SetCommonVersion(PROTOCOL_VERSION);
7988

8089
peerLogic->InitializeNode(&dummyNode1);
@@ -124,7 +133,16 @@ BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction)
124133
static void AddRandomOutboundPeer(std::vector<CNode*>& vNodes, PeerManager& peerLogic, ConnmanTestMsg& connman)
125134
{
126135
CAddress addr(ip(g_insecure_rand_ctx.randbits(32)), NODE_NONE);
127-
vNodes.emplace_back(new CNode(id++, ServiceFlags(NODE_NETWORK), INVALID_SOCKET, addr, /* nKeyedNetGroupIn */ 0, /* nLocalHostNonceIn */ 0, CAddress(), /* pszDest */ "", ConnectionType::OUTBOUND_FULL_RELAY, /* inbound_onion */ false));
136+
vNodes.emplace_back(new CNode{id++,
137+
ServiceFlags(NODE_NETWORK),
138+
/*sock=*/nullptr,
139+
addr,
140+
/*nKeyedNetGroupIn=*/0,
141+
/*nLocalHostNonceIn=*/0,
142+
CAddress(),
143+
/*addrNameIn=*/"",
144+
ConnectionType::OUTBOUND_FULL_RELAY,
145+
/*inbound_onion=*/false});
128146
CNode &node = *vNodes.back();
129147
node.SetCommonVersion(PROTOCOL_VERSION);
130148

@@ -220,7 +238,16 @@ BOOST_AUTO_TEST_CASE(peer_discouragement)
220238

221239
banman->ClearBanned();
222240
CAddress addr1(ip(0xa0b0c001), NODE_NONE);
223-
CNode dummyNode1(id++, NODE_NETWORK, INVALID_SOCKET, addr1, /* nKeyedNetGroupIn */ 0, /* nLocalHostNonceIn */ 0, CAddress(), /* pszDest */ "", ConnectionType::INBOUND, /* inbound_onion */ false);
241+
CNode dummyNode1{id++,
242+
NODE_NETWORK,
243+
/*sock=*/nullptr,
244+
addr1,
245+
/*nKeyedNetGroupIn=*/0,
246+
/*nLocalHostNonceIn=*/0,
247+
CAddress(),
248+
/*addrNameIn=*/"",
249+
ConnectionType::INBOUND,
250+
/*inbound_onion=*/false};
224251
dummyNode1.SetCommonVersion(PROTOCOL_VERSION);
225252
peerLogic->InitializeNode(&dummyNode1);
226253
dummyNode1.fSuccessfullyConnected = true;
@@ -233,7 +260,16 @@ BOOST_AUTO_TEST_CASE(peer_discouragement)
233260
BOOST_CHECK(!banman->IsDiscouraged(ip(0xa0b0c001|0x0000ff00))); // Different IP, not discouraged
234261

235262
CAddress addr2(ip(0xa0b0c002), NODE_NONE);
236-
CNode dummyNode2(id++, NODE_NETWORK, INVALID_SOCKET, addr2, /* nKeyedNetGroupIn */ 1, /* nLocalHostNonceIn */ 1, CAddress(), /* pszDest */ "", ConnectionType::INBOUND, /* inbound_onion */ false);
263+
CNode dummyNode2{id++,
264+
NODE_NETWORK,
265+
/*sock=*/nullptr,
266+
addr2,
267+
/*nKeyedNetGroupIn=*/1,
268+
/*nLocalHostNonceIn=*/1,
269+
CAddress(),
270+
/*pszDest=*/"",
271+
ConnectionType::INBOUND,
272+
/*inbound_onion=*/false};
237273
dummyNode2.SetCommonVersion(PROTOCOL_VERSION);
238274
peerLogic->InitializeNode(&dummyNode2);
239275
dummyNode2.fSuccessfullyConnected = true;
@@ -271,7 +307,16 @@ BOOST_AUTO_TEST_CASE(DoS_bantime)
271307
SetMockTime(nStartTime); // Overrides future calls to GetTime()
272308

273309
CAddress addr(ip(0xa0b0c001), NODE_NONE);
274-
CNode dummyNode(id++, NODE_NETWORK, INVALID_SOCKET, addr, /* nKeyedNetGroupIn */ 4, /* nLocalHostNonceIn */ 4, CAddress(), /* pszDest */ "", ConnectionType::INBOUND, /* inbound_onion */ false);
310+
CNode dummyNode{id++,
311+
NODE_NETWORK,
312+
/*sock=*/nullptr,
313+
addr,
314+
/*nKeyedNetGroupIn=*/4,
315+
/*nLocalHostNonceIn=*/4,
316+
CAddress(),
317+
/*addrNameIn=*/"",
318+
ConnectionType::INBOUND,
319+
/*inbound_onion=*/false};
275320
dummyNode.SetCommonVersion(PROTOCOL_VERSION);
276321
peerLogic->InitializeNode(&dummyNode);
277322
dummyNode.fSuccessfullyConnected = true;

src/test/fuzz/util.cpp

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
#include <util/time.h>
1111
#include <version.h>
1212

13+
#include <memory>
14+
1315
FuzzedSock::FuzzedSock(FuzzedDataProvider& fuzzed_data_provider)
1416
: m_fuzzed_data_provider{fuzzed_data_provider}
1517
{
@@ -155,6 +157,59 @@ int FuzzedSock::Connect(const sockaddr*, socklen_t) const
155157
return 0;
156158
}
157159

160+
int FuzzedSock::Bind(const sockaddr*, socklen_t) const
161+
{
162+
// Have a permanent error at bind_errnos[0] because when the fuzzed data is exhausted
163+
// SetFuzzedErrNo() will always set the global errno to bind_errnos[0]. We want to
164+
// avoid this method returning -1 and setting errno to a temporary error (like EAGAIN)
165+
// repeatedly because proper code should retry on temporary errors, leading to an
166+
// infinite loop.
167+
constexpr std::array bind_errnos{
168+
EACCES,
169+
EADDRINUSE,
170+
EADDRNOTAVAIL,
171+
EAGAIN,
172+
};
173+
if (m_fuzzed_data_provider.ConsumeBool()) {
174+
SetFuzzedErrNo(m_fuzzed_data_provider, bind_errnos);
175+
return -1;
176+
}
177+
return 0;
178+
}
179+
180+
int FuzzedSock::Listen(int) const
181+
{
182+
// Have a permanent error at listen_errnos[0] because when the fuzzed data is exhausted
183+
// SetFuzzedErrNo() will always set the global errno to listen_errnos[0]. We want to
184+
// avoid this method returning -1 and setting errno to a temporary error (like EAGAIN)
185+
// repeatedly because proper code should retry on temporary errors, leading to an
186+
// infinite loop.
187+
constexpr std::array listen_errnos{
188+
EADDRINUSE,
189+
EINVAL,
190+
EOPNOTSUPP,
191+
};
192+
if (m_fuzzed_data_provider.ConsumeBool()) {
193+
SetFuzzedErrNo(m_fuzzed_data_provider, listen_errnos);
194+
return -1;
195+
}
196+
return 0;
197+
}
198+
199+
std::unique_ptr<Sock> FuzzedSock::Accept(sockaddr* addr, socklen_t* addr_len) const
200+
{
201+
constexpr std::array accept_errnos{
202+
ECONNABORTED,
203+
EINTR,
204+
ENOMEM,
205+
};
206+
if (m_fuzzed_data_provider.ConsumeBool()) {
207+
SetFuzzedErrNo(m_fuzzed_data_provider, accept_errnos);
208+
return std::unique_ptr<FuzzedSock>();
209+
}
210+
return std::make_unique<FuzzedSock>(m_fuzzed_data_provider);
211+
}
212+
158213
int FuzzedSock::GetSockOpt(int level, int opt_name, void* opt_val, socklen_t* opt_len) const
159214
{
160215
constexpr std::array getsockopt_errnos{
@@ -174,6 +229,33 @@ int FuzzedSock::GetSockOpt(int level, int opt_name, void* opt_val, socklen_t* op
174229
return 0;
175230
}
176231

232+
int FuzzedSock::SetSockOpt(int, int, const void*, socklen_t) const
233+
{
234+
constexpr std::array setsockopt_errnos{
235+
ENOMEM,
236+
ENOBUFS,
237+
};
238+
if (m_fuzzed_data_provider.ConsumeBool()) {
239+
SetFuzzedErrNo(m_fuzzed_data_provider, setsockopt_errnos);
240+
return -1;
241+
}
242+
return 0;
243+
}
244+
245+
int FuzzedSock::GetSockName(sockaddr* name, socklen_t* name_len) const
246+
{
247+
constexpr std::array getsockname_errnos{
248+
ECONNRESET,
249+
ENOBUFS,
250+
};
251+
if (m_fuzzed_data_provider.ConsumeBool()) {
252+
SetFuzzedErrNo(m_fuzzed_data_provider, getsockname_errnos);
253+
return -1;
254+
}
255+
*name_len = m_fuzzed_data_provider.ConsumeData(name, *name_len);
256+
return 0;
257+
}
258+
177259
bool FuzzedSock::Wait(std::chrono::milliseconds timeout, Event requested, Event* occurred) const
178260
{
179261
constexpr std::array wait_errnos{

0 commit comments

Comments
 (0)