From 98ba5c796526d924a6033cb00c933e71cd86830d Mon Sep 17 00:00:00 2001 From: Vasil Dimov Date: Fri, 23 Aug 2024 15:36:40 +0200 Subject: [PATCH] net: split CConnman::BindListenPort() off CConnman Introduce a new low-level socket managing class `SockMan` and move the `CConnman::BindListenPort()` method to it. --- src/CMakeLists.txt | 1 + src/common/sockman.cpp | 85 ++++++++++++++++++++++++++++++++++++++++++ src/common/sockman.h | 44 ++++++++++++++++++++++ src/net.cpp | 72 +---------------------------------- src/net.h | 9 +---- 5 files changed, 133 insertions(+), 78 deletions(-) create mode 100644 src/common/sockman.cpp create mode 100644 src/common/sockman.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b762b03880d..ed37c69e555 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -123,6 +123,7 @@ add_library(bitcoin_common STATIC EXCLUDE_FROM_ALL common/run_command.cpp common/settings.cpp common/signmessage.cpp + common/sockman.cpp common/system.cpp common/url.cpp compressor.cpp diff --git a/src/common/sockman.cpp b/src/common/sockman.cpp new file mode 100644 index 00000000000..25527db8e07 --- /dev/null +++ b/src/common/sockman.cpp @@ -0,0 +1,85 @@ +// Copyright (c) 2024-present The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://opensource.org/license/mit/. + +#include // IWYU pragma: keep + +#include +#include +#include +#include + +bool SockMan::BindListenPort(const CService& addrBind, bilingual_str& strError) +{ + int nOne = 1; + + // Create socket for listening for incoming connections + struct sockaddr_storage sockaddr; + socklen_t len = sizeof(sockaddr); + if (!addrBind.GetSockAddr((struct sockaddr*)&sockaddr, &len)) + { + strError = Untranslated(strprintf("Bind address family for %s not supported", addrBind.ToStringAddrPort())); + LogPrintLevel(BCLog::NET, BCLog::Level::Error, "%s\n", strError.original); + return false; + } + + std::unique_ptr sock = CreateSock(addrBind.GetSAFamily(), SOCK_STREAM, IPPROTO_TCP); + if (!sock) { + strError = Untranslated(strprintf("Couldn't open socket for incoming connections (socket returned error %s)", NetworkErrorString(WSAGetLastError()))); + LogPrintLevel(BCLog::NET, BCLog::Level::Error, "%s\n", strError.original); + return false; + } + + // Allow binding if the port is still in TIME_WAIT state after + // the program was closed and restarted. + if (sock->SetSockOpt(SOL_SOCKET, SO_REUSEADDR, (sockopt_arg_type)&nOne, sizeof(int)) == SOCKET_ERROR) { + strError = Untranslated(strprintf("Error setting SO_REUSEADDR on socket: %s, continuing anyway", NetworkErrorString(WSAGetLastError()))); + LogPrintf("%s\n", strError.original); + } + + // some systems don't have IPV6_V6ONLY but are always v6only; others do have the option + // and enable it by default or not. Try to enable it, if possible. + if (addrBind.IsIPv6()) { +#ifdef IPV6_V6ONLY + if (sock->SetSockOpt(IPPROTO_IPV6, IPV6_V6ONLY, (sockopt_arg_type)&nOne, sizeof(int)) == SOCKET_ERROR) { + strError = Untranslated(strprintf("Error setting IPV6_V6ONLY on socket: %s, continuing anyway", NetworkErrorString(WSAGetLastError()))); + LogPrintf("%s\n", strError.original); + } +#endif +#ifdef WIN32 + int nProtLevel = PROTECTION_LEVEL_UNRESTRICTED; + if (sock->SetSockOpt(IPPROTO_IPV6, IPV6_PROTECTION_LEVEL, (const char*)&nProtLevel, sizeof(int)) == SOCKET_ERROR) { + strError = Untranslated(strprintf("Error setting IPV6_PROTECTION_LEVEL on socket: %s, continuing anyway", NetworkErrorString(WSAGetLastError()))); + LogPrintf("%s\n", strError.original); + } +#endif + } + + if (sock->Bind(reinterpret_cast(&sockaddr), len) == SOCKET_ERROR) { + int nErr = WSAGetLastError(); + if (nErr == WSAEADDRINUSE) + strError = strprintf(_("Unable to bind to %s on this computer. %s is probably already running."), addrBind.ToStringAddrPort(), CLIENT_NAME); + else + strError = strprintf(_("Unable to bind to %s on this computer (bind returned error %s)"), addrBind.ToStringAddrPort(), NetworkErrorString(nErr)); + LogPrintLevel(BCLog::NET, BCLog::Level::Error, "%s\n", strError.original); + return false; + } + LogPrintf("Bound to %s\n", addrBind.ToStringAddrPort()); + + // Listen for incoming connections + if (sock->Listen(SOMAXCONN) == SOCKET_ERROR) + { + strError = strprintf(_("Listening for incoming connections failed (listen returned error %s)"), NetworkErrorString(WSAGetLastError())); + LogPrintLevel(BCLog::NET, BCLog::Level::Error, "%s\n", strError.original); + return false; + } + + m_listen.emplace_back(std::move(sock)); + + return true; +} + +void SockMan::StopListening() +{ + m_listen.clear(); +} diff --git a/src/common/sockman.h b/src/common/sockman.h new file mode 100644 index 00000000000..84499cdacd9 --- /dev/null +++ b/src/common/sockman.h @@ -0,0 +1,44 @@ +// Copyright (c) 2024-present The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://opensource.org/license/mit/. + +#ifndef BITCOIN_COMMON_SOCKMAN_H +#define BITCOIN_COMMON_SOCKMAN_H + +#include +#include +#include + +#include +#include + +/** + * A socket manager class which handles socket operations. + * To use this class, inherit from it and implement the pure virtual methods. + * Handled operations: + * - binding and listening on sockets + */ +class SockMan +{ +public: + /** + * Bind to a new address:port, start listening and add the listen socket to `m_listen`. + * @param[in] addrBind Where to bind. + * @param[out] strError Error string if an error occurs. + * @retval true Success. + * @retval false Failure, `strError` will be set. + */ + bool BindListenPort(const CService& addrBind, bilingual_str& strError); + + /** + * Stop listening by closing all listening sockets. + */ + void StopListening(); + + /** + * List of listening sockets. + */ + std::vector> m_listen; +}; + +#endif // BITCOIN_COMMON_SOCKMAN_H diff --git a/src/net.cpp b/src/net.cpp index cccb7376562..67d5aea0266 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -3114,76 +3114,6 @@ void CConnman::ThreadI2PAcceptIncoming() } } -bool CConnman::BindListenPort(const CService& addrBind, bilingual_str& strError) -{ - int nOne = 1; - - // Create socket for listening for incoming connections - struct sockaddr_storage sockaddr; - socklen_t len = sizeof(sockaddr); - if (!addrBind.GetSockAddr((struct sockaddr*)&sockaddr, &len)) - { - strError = Untranslated(strprintf("Bind address family for %s not supported", addrBind.ToStringAddrPort())); - LogPrintLevel(BCLog::NET, BCLog::Level::Error, "%s\n", strError.original); - return false; - } - - std::unique_ptr sock = CreateSock(addrBind.GetSAFamily(), SOCK_STREAM, IPPROTO_TCP); - if (!sock) { - strError = Untranslated(strprintf("Couldn't open socket for incoming connections (socket returned error %s)", NetworkErrorString(WSAGetLastError()))); - LogPrintLevel(BCLog::NET, BCLog::Level::Error, "%s\n", strError.original); - return false; - } - - // Allow binding if the port is still in TIME_WAIT state after - // the program was closed and restarted. - if (sock->SetSockOpt(SOL_SOCKET, SO_REUSEADDR, (sockopt_arg_type)&nOne, sizeof(int)) == SOCKET_ERROR) { - strError = Untranslated(strprintf("Error setting SO_REUSEADDR on socket: %s, continuing anyway", NetworkErrorString(WSAGetLastError()))); - LogPrintf("%s\n", strError.original); - } - - // some systems don't have IPV6_V6ONLY but are always v6only; others do have the option - // and enable it by default or not. Try to enable it, if possible. - if (addrBind.IsIPv6()) { -#ifdef IPV6_V6ONLY - if (sock->SetSockOpt(IPPROTO_IPV6, IPV6_V6ONLY, (sockopt_arg_type)&nOne, sizeof(int)) == SOCKET_ERROR) { - strError = Untranslated(strprintf("Error setting IPV6_V6ONLY on socket: %s, continuing anyway", NetworkErrorString(WSAGetLastError()))); - LogPrintf("%s\n", strError.original); - } -#endif -#ifdef WIN32 - int nProtLevel = PROTECTION_LEVEL_UNRESTRICTED; - if (sock->SetSockOpt(IPPROTO_IPV6, IPV6_PROTECTION_LEVEL, (const char*)&nProtLevel, sizeof(int)) == SOCKET_ERROR) { - strError = Untranslated(strprintf("Error setting IPV6_PROTECTION_LEVEL on socket: %s, continuing anyway", NetworkErrorString(WSAGetLastError()))); - LogPrintf("%s\n", strError.original); - } -#endif - } - - if (sock->Bind(reinterpret_cast(&sockaddr), len) == SOCKET_ERROR) { - int nErr = WSAGetLastError(); - if (nErr == WSAEADDRINUSE) - strError = strprintf(_("Unable to bind to %s on this computer. %s is probably already running."), addrBind.ToStringAddrPort(), CLIENT_NAME); - else - strError = strprintf(_("Unable to bind to %s on this computer (bind returned error %s)"), addrBind.ToStringAddrPort(), NetworkErrorString(nErr)); - LogPrintLevel(BCLog::NET, BCLog::Level::Error, "%s\n", strError.original); - return false; - } - LogPrintf("Bound to %s\n", addrBind.ToStringAddrPort()); - - // Listen for incoming connections - if (sock->Listen(SOMAXCONN) == SOCKET_ERROR) - { - strError = strprintf(_("Listening for incoming connections failed (listen returned error %s)"), NetworkErrorString(WSAGetLastError())); - LogPrintLevel(BCLog::NET, BCLog::Level::Error, "%s\n", strError.original); - return false; - } - - m_listen.emplace_back(std::move(sock)); - - return true; -} - void Discover() { if (!fDiscover) @@ -3489,7 +3419,7 @@ void CConnman::StopNodes() } m_nodes_disconnected.clear(); m_listen_permissions.clear(); - m_listen.clear(); + StopListening(); semOutbound.reset(); semAddnode.reset(); } diff --git a/src/net.h b/src/net.h index bbee5b1fb71..cc9b82421ef 100644 --- a/src/net.h +++ b/src/net.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -1048,7 +1049,7 @@ protected: ~NetEventsInterface() = default; }; -class CConnman +class CConnman : private SockMan { public: @@ -1277,7 +1278,6 @@ private: //! in case of no limit, it will always return 0 std::chrono::seconds GetMaxOutboundTimeLeftInCycle_() const EXCLUSIVE_LOCKS_REQUIRED(m_total_bytes_sent_mutex); - bool BindListenPort(const CService& bindAddr, bilingual_str& strError); bool Bind(const CService& addr, unsigned int flags, NetPermissionFlags permissions); bool InitBinds(const Options& options); @@ -1417,11 +1417,6 @@ private: unsigned int nSendBufferMaxSize{0}; unsigned int nReceiveFloodSize{0}; - /** - * List of listening sockets. - */ - std::vector> m_listen; - /** * Permissions that incoming peers get based on our listening address they connected to. */