Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Robust ServerSocket not throw (EOL) on recoverable accept failure #10386

Merged
merged 2 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions common/Unit.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,15 @@ class UnitWSD : public UnitBase
return false;
}

// ---------------- ServerSocket hooks ----------------
/// Simulate `::accept` errors for external `ServerSocket::accept`. Implement unrecoverable errors by throwing an exception.
virtual bool simulateExternalAcceptError()
{
return false;
}
/// Simulate exceptions during `StreamSocket` constructor for external `ServerSocket::accept`.
virtual void simulateExternalSocketCtorException(std::shared_ptr<Socket>& /*socket*/) { }

// ---------------- TileCache hooks ----------------
/// Called before the lookupTile call returns. Should always be called to fire events.
virtual void lookupTile(int part, int mode, int width, int height, int tilePosX, int tilePosY,
Expand Down
14 changes: 4 additions & 10 deletions net/ServerSocket.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,22 +104,16 @@ class ServerSocket : public Socket
if (events & POLLIN)
{
std::shared_ptr<Socket> clientSocket = accept();
if (!clientSocket)
if (clientSocket)
{
const std::string msg = "Failed to accept. (errno: ";
throw std::runtime_error(msg + std::strerror(errno) + ')');
}
const size_t extConnCount = StreamSocket::getExternalConnectionCount();
if( 0 == net::Defaults.maxExtConnections || extConnCount <= net::Defaults.maxExtConnections )
{
LOG_TRC("Accepted client #" << clientSocket->getFD());
LOGA_TRC(Socket, "Accepted client #" << clientSocket->getFD() << ", " << *clientSocket);
_clientPoller.insertNewSocket(std::move(clientSocket));
} else
LOG_WRN("Limiter rejected extConn[" << extConnCount << "/" << net::Defaults.maxExtConnections << "]: " << *clientSocket);
}
}
}

protected:
bool isUnrecoverableAcceptError(const int cause);
/// Create a Socket instance from the accepted socket FD.
std::shared_ptr<Socket> createSocketFromAccept(int fd, Socket::Type type) const
{
Expand Down
128 changes: 93 additions & 35 deletions net/Socket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "TraceEvent.hpp"
#include "Util.hpp"

#include <cerrno>
#include <chrono>
#include <cstring>
#include <cctype>
Expand All @@ -26,6 +27,7 @@
#include <cstdio>
#include <string>
#include <unistd.h>
#include <sysexits.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
Expand Down Expand Up @@ -1149,63 +1151,115 @@ bool ServerSocket::bind([[maybe_unused]] Type type, [[maybe_unused]] int port)
#endif
}

bool ServerSocket::isUnrecoverableAcceptError(const int cause)
{
constexpr const char * messagePrefix = "Failed to accept. (errno: ";
switch(cause)
{
case EINTR:
case EAGAIN: // == EWOULDBLOCK
case ENETDOWN:
case EPROTO:
case ENOPROTOOPT:
case EHOSTDOWN:
#ifdef ENONET
case ENONET:
#endif
case EHOSTUNREACH:
case EOPNOTSUPP:
case ENETUNREACH:
case ECONNABORTED:
case ETIMEDOUT:
case EMFILE:
case ENFILE:
case ENOMEM:
case ENOBUFS:
{
LOG_DBG(messagePrefix << std::to_string(cause) << ", " << std::strerror(cause) << ')');
return false;
}
default:
{
LOG_FTL(messagePrefix << std::to_string(cause) << ", " << std::strerror(cause) << ')');
return true;
}
}
}

std::shared_ptr<Socket> ServerSocket::accept()
{
// Accept a connection (if any) and set it to non-blocking.
// There still need the client's address to filter request from POST(call from REST) here.
#if !MOBILEAPP
assert(_type != Socket::Type::Unix);

UnitWSD* const unitWsd = UnitWSD::isUnitTesting() ? &UnitWSD::get() : nullptr;
sgothel marked this conversation as resolved.
Show resolved Hide resolved
if (unitWsd && unitWsd->simulateExternalAcceptError())
return nullptr; // Recoverable error, ignore to retry

struct sockaddr_in6 clientInfo;
socklen_t addrlen = sizeof(clientInfo);
const int rc = ::accept4(getFD(), (struct sockaddr *)&clientInfo, &addrlen, SOCK_NONBLOCK | SOCK_CLOEXEC);
#else
const int rc = fakeSocketAccept4(getFD());
#endif
LOG_TRC("Accepted socket #" << rc << ", creating socket object.");
try
if (rc < 0)
{
// Create a socket object using the factory.
if (rc != -1)
{
if (isUnrecoverableAcceptError(errno))
Util::forcedExit(EX_SOFTWARE);
return nullptr;
}
LOG_TRC("Accepted socket #" << rc << ", creating socket object.");

#if !MOBILEAPP
char addrstr[INET6_ADDRSTRLEN];
char addrstr[INET6_ADDRSTRLEN];

Socket::Type type;
const void *inAddr;
if (clientInfo.sin6_family == AF_INET)
{
struct sockaddr_in *ipv4 = (struct sockaddr_in *)&clientInfo;
inAddr = &(ipv4->sin_addr);
type = Socket::Type::IPv4;
}
else
{
struct sockaddr_in6 *ipv6 = &clientInfo;
inAddr = &(ipv6->sin6_addr);
type = Socket::Type::IPv6;
}
Socket::Type type;
const void *inAddr;
if (clientInfo.sin6_family == AF_INET)
{
struct sockaddr_in *ipv4 = (struct sockaddr_in *)&clientInfo;
inAddr = &(ipv4->sin_addr);
type = Socket::Type::IPv4;
}
else
{
struct sockaddr_in6 *ipv6 = &clientInfo;
inAddr = &(ipv6->sin6_addr);
type = Socket::Type::IPv6;
}
::inet_ntop(clientInfo.sin6_family, inAddr, addrstr, sizeof(addrstr));
sgothel marked this conversation as resolved.
Show resolved Hide resolved

std::shared_ptr<Socket> _socket = createSocketFromAccept(rc, type);
const size_t extConnCount = StreamSocket::getExternalConnectionCount();
if (net::Defaults.maxExtConnections > 0 && extConnCount >= net::Defaults.maxExtConnections)
{
LOG_WRN("Limiter rejected extConn[" << extConnCount << "/" << net::Defaults.maxExtConnections << "]: #"
<< rc << " has family "
<< clientInfo.sin6_family << ", address " << addrstr << ":" << clientInfo.sin6_port);
::close(rc);
return nullptr;
}
try
{
// Create a socket object using the factory.
std::shared_ptr<Socket> _socket = createSocketFromAccept(rc, type);
if (unitWsd)
unitWsd->simulateExternalSocketCtorException(_socket);

::inet_ntop(clientInfo.sin6_family, inAddr, addrstr, sizeof(addrstr));
_socket->setClientAddress(addrstr, clientInfo.sin6_port);
_socket->setClientAddress(addrstr, clientInfo.sin6_port);

LOG_TRC("Accepted socket #" << _socket->getFD() << " has family "
<< clientInfo.sin6_family << ", " << *_socket);
#else
std::shared_ptr<Socket> _socket = createSocketFromAccept(rc, Socket::Type::Unix);
#endif
return _socket;
}
return std::shared_ptr<Socket>(nullptr);
LOG_TRC("Accepted socket #" << _socket->getFD() << " has family "
<< clientInfo.sin6_family << ", " << *_socket);
return _socket;
}
catch (const std::exception& ex)
{
LOG_ERR("Failed to create client socket #" << rc << ". Error: " << ex.what());
}

return nullptr;
#else
return createSocketFromAccept(rc, Socket::Type::Unix);
#endif
}

#if !MOBILEAPP
Expand Down Expand Up @@ -1251,11 +1305,15 @@ bool Socket::isLocal() const
std::shared_ptr<Socket> LocalServerSocket::accept()
{
const int rc = ::accept4(getFD(), nullptr, nullptr, SOCK_NONBLOCK | SOCK_CLOEXEC);
if (rc < 0)
{
if (isUnrecoverableAcceptError(errno))
Util::forcedExit(EX_SOFTWARE);
return nullptr;
}
try
{
LOG_DBG("Accepted prisoner socket #" << rc << ", creating socket object.");
if (rc < 0)
return std::shared_ptr<Socket>(nullptr);

std::shared_ptr<Socket> _socket = createSocketFromAccept(rc, Socket::Type::Unix);
// Sanity check this incoming socket
Expand Down Expand Up @@ -1305,8 +1363,8 @@ std::shared_ptr<Socket> LocalServerSocket::accept()
catch (const std::exception& ex)
{
LOG_ERR("Failed to create client socket #" << rc << ". Error: " << ex.what());
return std::shared_ptr<Socket>(nullptr);
}
return nullptr;
}

/// Returns true on success only.
Expand Down
6 changes: 6 additions & 0 deletions test/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ all_la_unit_tests = \
unit-timeout_inactive.la \
unit-timeout_conn.la \
unit-timeout_none.la \
unit-serversock_accept1.la \
unit-streamsock_ctor1.la \
unit-base.la
# unit-admin.la
# unit-tilecache.la # Empty test.
Expand Down Expand Up @@ -232,6 +234,10 @@ unit_timeout_conn_la_SOURCES = UnitTimeoutConnections.cpp
unit_timeout_conn_la_LIBADD = $(CPPUNIT_LIBS)
unit_timeout_none_la_SOURCES = UnitTimeoutNone.cpp
unit_timeout_none_la_LIBADD = $(CPPUNIT_LIBS)
unit_serversock_accept1_la_SOURCES = UnitServerSocketAcceptFailure1.cpp
unit_serversock_accept1_la_LIBADD = $(CPPUNIT_LIBS)
unit_streamsock_ctor1_la_SOURCES = UnitStreamSocketCtorFailure1.cpp
unit_streamsock_ctor1_la_LIBADD = $(CPPUNIT_LIBS)
unit_prefork_la_SOURCES = UnitPrefork.cpp
unit_prefork_la_LIBADD = $(CPPUNIT_LIBS)
unit_storage_la_SOURCES = UnitStorage.cpp
Expand Down
Loading
Loading