net: add RAII socket and use it instead of bare SOCKET

Introduce a class to manage the lifetime of a socket - when the object
that contains the socket goes out of scope, the underlying socket will
be closed.

In addition, the new `Sock` class has a `Send()`, `Recv()` and `Wait()`
methods that can be overridden by unit tests to mock the socket
operations.

The `Wait()` method also hides the
`#ifdef USE_POLL poll() #else select() #endif` technique from higher
level code.
This commit is contained in:
Vasil Dimov
2020-12-23 16:40:11 +01:00
parent dec9b5e850
commit ba9d73268f
7 changed files with 250 additions and 41 deletions

View File

@@ -15,7 +15,9 @@
#include <atomic>
#include <cstdint>
#include <functional>
#include <limits>
#include <memory>
#ifndef WIN32
#include <fcntl.h>
@@ -559,34 +561,28 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials
return true;
}
/**
* Try to create a socket file descriptor with specific properties in the
* communications domain (address family) of the specified service.
*
* For details on the desired properties, see the inline comments in the source
* code.
*/
SOCKET CreateSocket(const CService &addrConnect)
std::unique_ptr<Sock> CreateSockTCP(const CService& address_family)
{
// Create a sockaddr from the specified service.
struct sockaddr_storage sockaddr;
socklen_t len = sizeof(sockaddr);
if (!addrConnect.GetSockAddr((struct sockaddr*)&sockaddr, &len)) {
LogPrintf("Cannot create socket for %s: unsupported network\n", addrConnect.ToString());
return INVALID_SOCKET;
if (!address_family.GetSockAddr((struct sockaddr*)&sockaddr, &len)) {
LogPrintf("Cannot create socket for %s: unsupported network\n", address_family.ToString());
return nullptr;
}
// Create a TCP socket in the address family of the specified service.
SOCKET hSocket = socket(((struct sockaddr*)&sockaddr)->sa_family, SOCK_STREAM, IPPROTO_TCP);
if (hSocket == INVALID_SOCKET)
return INVALID_SOCKET;
if (hSocket == INVALID_SOCKET) {
return nullptr;
}
// Ensure that waiting for I/O on this socket won't result in undefined
// behavior.
if (!IsSelectableSocket(hSocket)) {
CloseSocket(hSocket);
LogPrintf("Cannot create connection: non-selectable socket created (fd >= FD_SETSIZE ?)\n");
return INVALID_SOCKET;
return nullptr;
}
#ifdef SO_NOSIGPIPE
@@ -602,11 +598,14 @@ SOCKET CreateSocket(const CService &addrConnect)
// Set the non-blocking option on the socket.
if (!SetSocketNonBlocking(hSocket, true)) {
CloseSocket(hSocket);
LogPrintf("CreateSocket: Setting socket to non-blocking failed, error %s\n", NetworkErrorString(WSAGetLastError()));
LogPrintf("Error setting socket to non-blocking: %s\n", NetworkErrorString(WSAGetLastError()));
return nullptr;
}
return hSocket;
return std::make_unique<Sock>(hSocket);
}
std::function<std::unique_ptr<Sock>(const CService&)> CreateSock = CreateSockTCP;
template<typename... Args>
static void LogConnectFailure(bool manual_connection, const char* fmt, const Args&... args) {
std::string error_message = tfm::format(fmt, args...);