Skip to content

Commit 6300b95

Browse files
committed
Merge bitcoin/bitcoin#24357: refactor: make setsockopt() and SetSocketNoDelay() mockable/testable
a2c4a7a net: use Sock::SetSockOpt() instead of standalone SetSocketNoDelay() (Vasil Dimov) d65b6c3 net: use Sock::SetSockOpt() instead of setsockopt() (Vasil Dimov) 184e56d net: add new method Sock::SetSockOpt() that wraps setsockopt() (Vasil Dimov) Pull request description: _This is a piece of #21878, chopped off to ease review._ Add a `virtual` (thus mockable) method `Sock::SetSockOpt()` that wraps the system `setsockopt()`. Convert the standalone `SetSocketNoDelay()` function to a `virtual` (thus mockable) method `Sock::SetNoDelay()`. This will help avoid syscalls during testing and to mock them to return whatever is suitable for the tests. ACKs for top commit: laanwj: Code review ACK a2c4a7a jonatack: ACK a2c4a7a change since last review is folding `Sock::SetNoDelay()` into the callers Tree-SHA512: 3e2b016c1e4128317a28c17dc9b30472949e1ac3b071b2697c6d30cbcc830df1ee4392a4e23b2ea1ab4e3fb0f59ef450e2a4f3c1df3d8c803dd081652b6c7387
2 parents f8b2e9b + a2c4a7a commit 6300b95

File tree

8 files changed

+62
-20
lines changed

8 files changed

+62
-20
lines changed

src/net.cpp

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1190,7 +1190,11 @@ void CConnman::CreateNodeFromAcceptedSocket(std::unique_ptr<Sock>&& sock,
11901190

11911191
// According to the internet TCP_NODELAY is not carried into accepted sockets
11921192
// on all platforms. Set it again here just to be sure.
1193-
SetSocketNoDelay(sock->Get());
1193+
const int on{1};
1194+
if (sock->SetSockOpt(IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)) == SOCKET_ERROR) {
1195+
LogPrint(BCLog::NET, "connection from %s: unable to set TCP_NODELAY, continuing anyway\n",
1196+
addr.ToString());
1197+
}
11941198

11951199
// Don't accept connections from banned peers.
11961200
bool banned = m_banman && m_banman->IsBanned(addr);
@@ -2395,17 +2399,26 @@ bool CConnman::BindListenPort(const CService& addrBind, bilingual_str& strError,
23952399

23962400
// Allow binding if the port is still in TIME_WAIT state after
23972401
// the program was closed and restarted.
2398-
setsockopt(sock->Get(), SOL_SOCKET, SO_REUSEADDR, (sockopt_arg_type)&nOne, sizeof(int));
2402+
if (sock->SetSockOpt(SOL_SOCKET, SO_REUSEADDR, (sockopt_arg_type)&nOne, sizeof(int)) == SOCKET_ERROR) {
2403+
strError = strprintf(Untranslated("Error setting SO_REUSEADDR on socket: %s, continuing anyway"), NetworkErrorString(WSAGetLastError()));
2404+
LogPrintf("%s\n", strError.original);
2405+
}
23992406

24002407
// some systems don't have IPV6_V6ONLY but are always v6only; others do have the option
24012408
// and enable it by default or not. Try to enable it, if possible.
24022409
if (addrBind.IsIPv6()) {
24032410
#ifdef IPV6_V6ONLY
2404-
setsockopt(sock->Get(), IPPROTO_IPV6, IPV6_V6ONLY, (sockopt_arg_type)&nOne, sizeof(int));
2411+
if (sock->SetSockOpt(IPPROTO_IPV6, IPV6_V6ONLY, (sockopt_arg_type)&nOne, sizeof(int)) == SOCKET_ERROR) {
2412+
strError = strprintf(Untranslated("Error setting IPV6_V6ONLY on socket: %s, continuing anyway"), NetworkErrorString(WSAGetLastError()));
2413+
LogPrintf("%s\n", strError.original);
2414+
}
24052415
#endif
24062416
#ifdef WIN32
24072417
int nProtLevel = PROTECTION_LEVEL_UNRESTRICTED;
2408-
setsockopt(sock->Get(), IPPROTO_IPV6, IPV6_PROTECTION_LEVEL, (const char*)&nProtLevel, sizeof(int));
2418+
if (sock->SetSockOpt(IPPROTO_IPV6, IPV6_PROTECTION_LEVEL, (const char*)&nProtLevel, sizeof(int)) == SOCKET_ERROR) {
2419+
strError = strprintf(Untranslated("Error setting IPV6_PROTECTION_LEVEL on socket: %s, continuing anyway"), NetworkErrorString(WSAGetLastError()));
2420+
LogPrintf("%s\n", strError.original);
2421+
}
24092422
#endif
24102423
}
24112424

src/netbase.cpp

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

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

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

520527
// Set the non-blocking option on the socket.
521-
if (!SetSocketNonBlocking(hSocket)) {
522-
CloseSocket(hSocket);
528+
if (!SetSocketNonBlocking(sock->Get())) {
523529
LogPrintf("Error setting socket to non-blocking: %s\n", NetworkErrorString(WSAGetLastError()));
524530
return nullptr;
525531
}
526-
return std::make_unique<Sock>(hSocket);
532+
return sock;
527533
}
528534

529535
std::function<std::unique_ptr<Sock>(const CService&)> CreateSock = CreateSockTCP;
@@ -726,13 +732,6 @@ bool SetSocketNonBlocking(const SOCKET& hSocket)
726732
return true;
727733
}
728734

729-
bool SetSocketNoDelay(const SOCKET& hSocket)
730-
{
731-
int set = 1;
732-
int rc = setsockopt(hSocket, IPPROTO_TCP, TCP_NODELAY, (const char*)&set, sizeof(int));
733-
return rc == 0;
734-
}
735-
736735
void InterruptSocks5(bool interrupt)
737736
{
738737
interruptSocks5Recv = interrupt;

src/netbase.h

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

224224
/** Enable non-blocking mode for a socket */
225225
bool SetSocketNonBlocking(const SOCKET& hSocket);
226-
/** Set the TCP_NODELAY flag on a socket */
227-
bool SetSocketNoDelay(const SOCKET& hSocket);
228226
void InterruptSocks5(bool interrupt);
229227

230228
/**

src/test/fuzz/util.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,19 @@ int FuzzedSock::GetSockOpt(int level, int opt_name, void* opt_val, socklen_t* op
193193
return 0;
194194
}
195195

196+
int FuzzedSock::SetSockOpt(int, int, const void*, socklen_t) const
197+
{
198+
constexpr std::array setsockopt_errnos{
199+
ENOMEM,
200+
ENOBUFS,
201+
};
202+
if (m_fuzzed_data_provider.ConsumeBool()) {
203+
SetFuzzedErrNo(m_fuzzed_data_provider, setsockopt_errnos);
204+
return -1;
205+
}
206+
return 0;
207+
}
208+
196209
bool FuzzedSock::Wait(std::chrono::milliseconds timeout, Event requested, Event* occurred) const
197210
{
198211
constexpr std::array wait_errnos{

src/test/fuzz/util.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ class FuzzedSock : public Sock
6868

6969
int GetSockOpt(int level, int opt_name, void* opt_val, socklen_t* opt_len) const override;
7070

71+
int SetSockOpt(int level, int opt_name, const void* opt_val, socklen_t opt_len) const override;
72+
7173
bool Wait(std::chrono::milliseconds timeout, Event requested, Event* occurred = nullptr) const override;
7274

7375
bool IsConnected(std::string& errmsg) const override;

src/test/util/net.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@ class StaticContentsSock : public Sock
150150
return 0;
151151
}
152152

153+
int SetSockOpt(int, int, const void*, socklen_t) const override { return 0; }
154+
153155
bool Wait(std::chrono::milliseconds timeout,
154156
Event requested,
155157
Event* occurred = nullptr) const override

src/util/sock.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,11 @@ int Sock::GetSockOpt(int level, int opt_name, void* opt_val, socklen_t* opt_len)
105105
return getsockopt(m_socket, level, opt_name, static_cast<char*>(opt_val), opt_len);
106106
}
107107

108+
int Sock::SetSockOpt(int level, int opt_name, const void* opt_val, socklen_t opt_len) const
109+
{
110+
return setsockopt(m_socket, level, opt_name, static_cast<const char*>(opt_val), opt_len);
111+
}
112+
108113
bool Sock::Wait(std::chrono::milliseconds timeout, Event requested, Event* occurred) const
109114
{
110115
#ifdef USE_POLL

src/util/sock.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,16 @@ class Sock
115115
void* opt_val,
116116
socklen_t* opt_len) const;
117117

118+
/**
119+
* setsockopt(2) wrapper. Equivalent to
120+
* `setsockopt(this->Get(), level, opt_name, opt_val, opt_len)`. Code that uses this
121+
* wrapper can be unit tested if this method is overridden by a mock Sock implementation.
122+
*/
123+
[[nodiscard]] virtual int SetSockOpt(int level,
124+
int opt_name,
125+
const void* opt_val,
126+
socklen_t opt_len) const;
127+
118128
using Event = uint8_t;
119129

120130
/**

0 commit comments

Comments
 (0)