http: Introduce HTTPServer class and implement binding to listening socket

Introduce a new low-level socket managing class `HTTPServer`.

BindAndStartListening() was copied from CConnMan's BindListenPort()
in net.cpp and modernized.

Unit-test it with a new class `SocketTestingSetup` which mocks
`CreateSock()` and will enable mock client I/O in future commits.

Co-authored-by: Vasil Dimov <vd@FreeBSD.org>
This commit is contained in:
Matthew Zipkin
2025-06-04 13:52:56 -04:00
parent 9463e98781
commit f5bc018948
5 changed files with 178 additions and 1 deletions

View File

@@ -2,6 +2,8 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <bitcoin-build-config.h> // IWYU pragma: keep
#include <httpserver.h>
#include <chainparamsbase.h>
@@ -15,6 +17,7 @@
#include <sync.h>
#include <util/check.h>
#include <util/signalinterrupt.h>
#include <util/sock.h>
#include <util/strencodings.h>
#include <util/threadnames.h>
#include <util/threadpool.h>
@@ -46,6 +49,9 @@
#include <support/events.h>
//! Explicit alias for setting socket option methods.
static constexpr int SOCKET_OPTION_TRUE{1};
using common::InvalidPortErrMsg;
using http_libevent::HTTPRequest;
@@ -889,4 +895,85 @@ bool HTTPRequest::LoadBody(LineReader& reader)
return true;
}
util::Expected<void, std::string> HTTPServer::BindAndStartListening(const CService& to)
{
// Create socket for listening for incoming connections
sockaddr_storage storage;
auto sa = reinterpret_cast<sockaddr*>(&storage);
socklen_t len{sizeof(storage)};
if (!to.GetSockAddr(sa, &len)) {
return util::Unexpected{strprintf("Bind address family for %s not supported", to.ToStringAddrPort())};
}
std::unique_ptr<Sock> sock{CreateSock(to.GetSAFamily(), SOCK_STREAM, IPPROTO_TCP)};
if (!sock) {
return util::Unexpected{strprintf("Cannot create %s listen socket: %s",
to.ToStringAddrPort(),
NetworkErrorString(WSAGetLastError()))};
}
// 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, &SOCKET_OPTION_TRUE, sizeof(SOCKET_OPTION_TRUE)) == SOCKET_ERROR) {
LogDebug(BCLog::HTTP,
"Cannot set SO_REUSEADDR on %s listen socket: %s, continuing anyway",
to.ToStringAddrPort(),
NetworkErrorString(WSAGetLastError()));
}
// 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 (to.IsIPv6()) {
#ifdef IPV6_V6ONLY
if (sock->SetSockOpt(IPPROTO_IPV6, IPV6_V6ONLY, &SOCKET_OPTION_TRUE, sizeof(SOCKET_OPTION_TRUE)) == SOCKET_ERROR) {
LogDebug(BCLog::HTTP,
"Cannot set IPV6_V6ONLY on %s listen socket: %s, continuing anyway",
to.ToStringAddrPort(),
NetworkErrorString(WSAGetLastError()));
}
#endif
#ifdef WIN32
int prot_level{PROTECTION_LEVEL_UNRESTRICTED};
if (sock->SetSockOpt(IPPROTO_IPV6,
IPV6_PROTECTION_LEVEL,
&prot_level,
sizeof(prot_level)) == SOCKET_ERROR) {
LogDebug(BCLog::HTTP,
"Cannot set IPV6_PROTECTION_LEVEL on %s listen socket: %s, continuing anyway",
to.ToStringAddrPort(),
NetworkErrorString(WSAGetLastError()));
}
#endif
}
if (sock->Bind(sa, len) == SOCKET_ERROR) {
const int err{WSAGetLastError()};
if (err == WSAEADDRINUSE) {
return util::Unexpected{strprintf("Unable to bind to %s on this computer. %s is probably already running.",
to.ToStringAddrPort(),
CLIENT_NAME)};
} else {
return util::Unexpected{strprintf("Unable to bind to %s on this computer (bind returned error %s)",
to.ToStringAddrPort(),
NetworkErrorString(err))};
}
}
// Listen for incoming connections
if (sock->Listen(SOMAXCONN) == SOCKET_ERROR) {
return util::Unexpected{strprintf("Cannot listen on %s: %s",
to.ToStringAddrPort(),
NetworkErrorString(WSAGetLastError()))};
}
m_listen.emplace_back(std::move(sock));
return {};
}
void HTTPServer::StopListening()
{
m_listen.clear();
}
} // namespace http_bitcoin