diff --git a/src/httpserver.cpp b/src/httpserver.cpp index 310907ba691..0bda2843fe9 100644 --- a/src/httpserver.cpp +++ b/src/httpserver.cpp @@ -976,4 +976,36 @@ void HTTPServer::StopListening() { m_listen.clear(); } + +std::unique_ptr HTTPServer::AcceptConnection(const Sock& listen_sock, CService& addr) +{ + // Make sure we only operate on our own listening sockets + Assume(std::ranges::any_of(m_listen, [&](const auto& sock) { return sock.get() == &listen_sock; })); + + sockaddr_storage storage; + socklen_t len{sizeof(storage)}; + auto sa = reinterpret_cast(&storage); + + auto sock{listen_sock.Accept(sa, &len)}; + + if (!sock) { + const int err{WSAGetLastError()}; + if (err != WSAEWOULDBLOCK) { + LogDebug(BCLog::HTTP, + "Cannot accept new connection: %s", + NetworkErrorString(err)); + } + return {}; + } + + // The OS handed us a valid socket but we can't determine its source address. + // In the unlikely event this occurs, the invalid address will be rejected + // by the downstream ClientAllowed() check. + if (!addr.SetSockAddr(sa, len)) { + LogDebug(BCLog::HTTP, + "Unknown socket family"); + } + + return sock; +} } // namespace http_bitcoin diff --git a/src/httpserver.h b/src/httpserver.h index ba93cbb8b97..e4dabc632e2 100644 --- a/src/httpserver.h +++ b/src/httpserver.h @@ -323,11 +323,29 @@ public: */ size_t GetListeningSocketCount() const { return m_listen.size(); } + /** + * This is a temporary method used to accept connections from a listening + * socket in the unit tests before the I/O loop is implemented. + * It will be removed in a future commit. + */ + std::unique_ptr AcceptConnectionFromListeningSocket(CService& addr) + { + return AcceptConnection(*m_listen.front(), addr); + } + private: /** * List of listening sockets. */ std::vector> m_listen; + + /** + * Accept a connection. + * @param[in] listen_sock Socket on which to accept the connection. + * @param[out] addr Address of the peer that was accepted. + * @return Newly created socket for the accepted connection. + */ + std::unique_ptr AcceptConnection(const Sock& listen_sock, CService& addr); }; } // namespace http_bitcoin diff --git a/src/test/httpserver_tests.cpp b/src/test/httpserver_tests.cpp index 1114df05f1c..22af3981f2d 100644 --- a/src/test/httpserver_tests.cpp +++ b/src/test/httpserver_tests.cpp @@ -395,5 +395,15 @@ BOOST_AUTO_TEST_CASE(http_server_socket_tests) BOOST_REQUIRE(server.BindAndStartListening(addr_bind)); // We are bound and listening BOOST_REQUIRE_EQUAL(server.GetListeningSocketCount(), 1); + + // Pick up the phone, there's no one there + CService addr_connection; + BOOST_REQUIRE(!server.AcceptConnectionFromListeningSocket(addr_connection)); + + // Create a mock client and add it to the local CreateSock queue + ConnectClient(); + // Accept the connection + BOOST_REQUIRE(server.AcceptConnectionFromListeningSocket(addr_connection)); + BOOST_CHECK_EQUAL(addr_connection.ToStringAddrPort(), "5.5.5.5:6789"); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index 2ba63efd11f..846f47032aa 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -658,6 +658,21 @@ SocketTestingSetup::~SocketTestingSetup() CreateSock = m_create_sock_orig; } +void SocketTestingSetup::ConnectClient() +{ + // I/O pipes for a mock Connected Socket we can read and write to. + auto connected_socket_pipes(std::make_shared()); + + // TODO: Insert a payload + + // Create the Mock Connected Socket that represents a client. + // It needs I/O pipes but its queue can remain empty + std::unique_ptr connected_socket{std::make_unique(connected_socket_pipes)}; + + // Push into the queue of Accepted Sockets returned by the local CreateSock() + m_accepted_sockets.Push(std::move(connected_socket)); +} + /** * @returns a real block (0000000000013b8ab2cd513b0261a14096412195a72a0c4827d229dcc7e0f7af) * with 9 txs. diff --git a/src/test/util/setup_common.h b/src/test/util/setup_common.h index 7a8f6b70c8c..bf9ec42ebf2 100644 --- a/src/test/util/setup_common.h +++ b/src/test/util/setup_common.h @@ -256,6 +256,9 @@ public: explicit SocketTestingSetup(); ~SocketTestingSetup(); + //! Connect to the socket with a mock client (a DynSock) + void ConnectClient(); + private: //! Save the original value of CreateSock here and restore it when the test ends. decltype(CreateSock) m_create_sock_orig;