Skip to content

Commit 013278f

Browse files
author
Sven Göthel
committed
cool#9833: Implement MaxConnection Limit (2b)
Reimplementation of commit 80246f7 (post revert). Adding TCP connection limit to server-side TCP IPv4/IPv6 Sockets - Validated and registered at StreamSocket::create<T>() - Only limits TCP connections for server-side IPv4 or IPv6 TCP connections. - If rejected (limit reached), the accepted FD gets released immediately and the TSocket ctor only receives a -1 FD, i.e. !isOpen(). This also signals `ServerSocket::handlePoll` to dismiss the instance. - If counted against max TCP connections, Socket::_isCounted is set true (see below) - Unregistered at Socket dtor post socket closing - If counted against max TCP connections (_isCounted) `decrTCPConnCount` is called after socket ::close (returned to system). TODO: - revise net::Defaults.maxTCPConnections to match actual system settings Signed-off-by: Sven Göthel <[email protected]> Change-Id: Ib9f0ac17c05ffe65c2370490f68f581fa76730e7
1 parent 85dd972 commit 013278f

File tree

4 files changed

+123
-17
lines changed

4 files changed

+123
-17
lines changed

net/ServerSocket.hpp

+5-3
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,11 @@ class ServerSocket : public Socket
108108
const std::string msg = "Failed to accept. (errno: ";
109109
throw std::runtime_error(msg + std::strerror(errno) + ')');
110110
}
111-
112-
LOG_TRC("Accepted client #" << clientSocket->getFD());
113-
_clientPoller.insertNewSocket(std::move(clientSocket));
111+
if( clientSocket->isOpen() )
112+
{
113+
LOG_TRC("Accepted client #" << clientSocket->getFD());
114+
_clientPoller.insertNewSocket(std::move(clientSocket));
115+
} // else intentionally cancelled accepted connection (e.g. connection limiter)
114116
}
115117
}
116118

net/Socket.cpp

+14-7
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ std::atomic<bool> Socket::InhibitThreadChecks(false);
6565

6666
std::unique_ptr<Watchdog> SocketPoll::PollWatchdog;
6767

68+
std::mutex Socket::maxTCPConnMutex;
69+
size_t Socket::maxTCPConnCount = 0;
70+
6871
net::DefaultValues net::Defaults = { .inactivityTimeout = std::chrono::seconds(3600),
6972
.wsPingAvgTimeout = std::chrono::seconds(12),
7073
.wsPingInterval = std::chrono::seconds(18),
@@ -146,8 +149,10 @@ std::string Socket::getStatsString(const std::chrono::steady_clock::time_point &
146149
std::ostream& Socket::streamImpl(std::ostream& os) const
147150
{
148151
os << "Socket[#" << getFD()
149-
<< ", " << toString(type())
150-
<< " @ ";
152+
<< ", " << toString(type());
153+
if (isCounted())
154+
os << ", counted";
155+
os << " @ ";
151156
if (Type::IPv6 == type())
152157
{
153158
os << "[" << clientAddress() << "]:" << clientPort();
@@ -1188,9 +1193,9 @@ std::shared_ptr<Socket> ServerSocket::accept()
11881193
::inet_ntop(clientInfo.sin6_family, inAddr, addrstr, sizeof(addrstr));
11891194
_socket->setClientAddress(addrstr, clientInfo.sin6_port);
11901195

1191-
LOG_TRC("Accepted socket #" << _socket->getFD() << " has family "
1192-
<< clientInfo.sin6_family << " address "
1193-
<< _socket->clientAddress());
1196+
LOG_TRC((_socket->isOpen() ? "Accepted":"TCP Limiter rejected")
1197+
<< " socket #" << rc << " has family "
1198+
<< clientInfo.sin6_family << ", " << *_socket);
11941199
#else
11951200
std::shared_ptr<Socket> _socket = createSocketFromAccept(rc, Socket::Type::Unix);
11961201
#endif
@@ -1393,8 +1398,10 @@ std::ostream& StreamSocket::stream(std::ostream& os) const
13931398
{
13941399
os << "StreamSocket[#" << getFD()
13951400
<< ", " << toStringShort(_wsState)
1396-
<< ", " << Socket::toString(type())
1397-
<< " @ ";
1401+
<< ", " << Socket::toString(type());
1402+
if (isCounted())
1403+
os << ", counted";
1404+
os << " @ ";
13981405
if (Type::IPv6 == type())
13991406
{
14001407
os << "[" << clientAddress() << "]:" << clientPort();

net/Socket.hpp

+98-7
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333

3434
#include <common/StateEnum.hpp>
3535
#include "Log.hpp"
36+
#include "NetUtil.hpp"
3637
#include "Util.hpp"
3738
#include "Buffer.hpp"
3839
#include "SigUtil.hpp"
@@ -129,6 +130,8 @@ class SocketDisposition final
129130
std::shared_ptr<Socket> _socket;
130131
};
131132

133+
class StreamSocket; // fwd
134+
132135
/// A non-blocking, streaming socket.
133136
class Socket
134137
{
@@ -151,6 +154,7 @@ class Socket
151154
, _lastSeenTime(_creationTime)
152155
, _bytesSent(0)
153156
, _bytesRcvd(0)
157+
, _isCounted(false)
154158
{
155159
init();
156160
}
@@ -162,19 +166,25 @@ class Socket
162166
// Doesn't block on sockets; no error handling needed.
163167
if constexpr (!Util::isMobileApp())
164168
{
165-
::close(_fd);
169+
if (::close(_fd))
170+
LOG_SYS("Ignored error closing socket #" << _fd);
166171
LOG_DBG("Closed socket " << toStringImpl());
167172
}
168173
else
169-
{
170174
fakeSocketClose(_fd);
175+
if (_isCounted)
176+
{
177+
_isCounted = false;
178+
decrTCPConnCount(_fd);
171179
}
172180
}
173181

174182
/// Returns true if this socket is open, i.e. allowed to be polled and not shutdown
175183
bool isOpen() const { return _open; }
176184
/// Returns true if this socket has been closed, i.e. rejected from polling and potentially shutdown
177185
bool isClosed() const { return !_open; }
186+
/// Returns true if this socket is counted against free maximum TCP connections
187+
bool isCounted() const { return _isCounted; }
178188

179189
constexpr Type type() const { return _type; }
180190
constexpr bool isIPType() const { return Type::IPv4 == _type || Type::IPv6 == _type; }
@@ -249,7 +259,7 @@ class Socket
249259
if constexpr (!Util::isMobileApp())
250260
{
251261
const int val = 1;
252-
if (::setsockopt(_fd, IPPROTO_TCP, TCP_NODELAY, (char*)&val, sizeof(val)) == -1)
262+
if (isOpen() && ::setsockopt(_fd, IPPROTO_TCP, TCP_NODELAY, (char*)&val, sizeof(val)) == -1)
253263
{
254264
static std::once_flag once;
255265
std::call_once(once,
@@ -409,6 +419,14 @@ class Socket
409419
LOG_TRC("Ignore further input on socket.");
410420
_ignoreInput = true;
411421
}
422+
423+
/// Returns connection count
424+
static size_t getConnectionCount()
425+
{
426+
std::lock_guard<std::mutex> lock(maxTCPConnMutex);
427+
return maxTCPConnCount;
428+
}
429+
412430
protected:
413431
/// Construct based on an existing socket fd.
414432
/// Used by accept() only.
@@ -422,6 +440,7 @@ class Socket
422440
, _lastSeenTime(_creationTime)
423441
, _bytesSent(0)
424442
, _bytesRcvd(0)
443+
, _isCounted(false)
425444
{
426445
init();
427446
}
@@ -493,6 +512,53 @@ class Socket
493512

494513
/// We check the owner even in the release builds, needs to be always correct.
495514
std::thread::id _owner;
515+
516+
/// true if counted against net::Defaults.maxTCPConnections and dtor must call `decrTCPConnCount`
517+
/// post socket ::close where the socket has been returned to the system.
518+
bool _isCounted;
519+
520+
/// Decrements global connection count
521+
/// Call this after socket ::close, where it has been returned to the system.
522+
/// @param fd the related file descriptor for logging
523+
static void decrTCPConnCount(int fd)
524+
{
525+
std::lock_guard<std::mutex> lock(maxTCPConnMutex);
526+
const size_t u = maxTCPConnCount;
527+
auto logPrefix = [fd](std::ostream& os) { os << '#' << fd << ": "; };
528+
if (u > 0)
529+
{
530+
const size_t v = --maxTCPConnCount;
531+
LOG_TRC("TCP Limiter: Count decremented: " << v);
532+
}
533+
else
534+
LOG_WRN("TCP Limiter: Count decrement underflow: " << u);
535+
}
536+
537+
/// Increments global TCP connection counter if not exceeding net::DefaultValue::maxTCPConnections.
538+
/// Returns true if free connections were available, otherwise false.
539+
/// No limitation is applied if net::DefaultValue::maxTCPConnections == 0.
540+
/// @param fd the related file descriptor for logging
541+
static bool checkAndIncrTCPConnCount(int fd)
542+
{
543+
std::lock_guard<std::mutex> lock(maxTCPConnMutex);
544+
const size_t u = maxTCPConnCount;
545+
auto logPrefix = [fd](std::ostream& os) { os << '#' << fd << ": "; };
546+
if (net::Defaults.maxTCPConnections == 0 || u < net::Defaults.maxTCPConnections)
547+
{
548+
const size_t v = ++maxTCPConnCount;
549+
LOG_TRC("TCP Limiter: Count incremented: " << v);
550+
return true;
551+
}
552+
else
553+
{
554+
LOG_WRN("TCP Limiter: Limit reached: " << u);
555+
return false;
556+
}
557+
}
558+
friend class StreamSocket; // allow `checkAndIncrConnectionCount`
559+
560+
static std::mutex maxTCPConnMutex;
561+
static size_t maxTCPConnCount; // accepted TCP IPv4/IPv6 socket count
496562
};
497563

498564
inline std::ostream& operator<<(std::ostream& os, const Socket &s) { return s.stream(os); }
@@ -1322,11 +1388,12 @@ class StreamSocket : public Socket,
13221388
_socketHandler.reset();
13231389
}
13241390

1325-
/// Create a socket of type TSocket given an FD and a handler.
1391+
/// Create a socket of type TSocket derived from StreamSocket given an FD and a handler.
13261392
/// We need this helper since the handler needs a shared_ptr to the socket
13271393
/// but we can't have a shared_ptr in the ctor.
1328-
template <typename TSocket>
1329-
static std::shared_ptr<TSocket> create(std::string hostname, const int fd, Type type,
1394+
template <typename TSocket,
1395+
std::enable_if_t< std::is_base_of_v<StreamSocket, TSocket>, bool> = true>
1396+
static std::shared_ptr<TSocket> create(std::string hostname, int fd, Type type,
13301397
bool isClient, HostType hostType,
13311398
std::shared_ptr<ProtocolHandlerInterface> handler,
13321399
ReadType readType = ReadType::NormalRead,
@@ -1337,7 +1404,31 @@ class StreamSocket : public Socket,
13371404
throw std::runtime_error("StreamSocket " + std::to_string(fd) +
13381405
" expects a valid SocketHandler instance.");
13391406

1340-
auto socket = std::make_shared<TSocket>(std::move(hostname), fd, type, isClient, hostType, readType, creationTime);
1407+
bool isCounted = false;
1408+
if (!isClient && fd >= 0 && (type == Type::IPv4 || type == Type::IPv6))
1409+
{
1410+
/// Only limit TCP connections for server-side IPv4 or IPv6 TCP connections.
1411+
///
1412+
/// If limited, the accepted FD gets released immediately
1413+
/// and the TSocket ctor only receives a -1 FD, i.e. !isOpen(),
1414+
/// which also signals ServerSocket::handlePoll to dismiss the instance.
1415+
if (!Socket::checkAndIncrTCPConnCount(fd))
1416+
{
1417+
if constexpr (!Util::isMobileApp())
1418+
{
1419+
auto logPrefix = [fd](std::ostream& os) { os << '#' << fd << ": "; };
1420+
if (::close(fd))
1421+
LOG_SYS("Ignored error closing socket #" << fd);
1422+
}
1423+
else
1424+
fakeSocketClose(fd);
1425+
fd = -1; // closed!
1426+
} else
1427+
isCounted = true;
1428+
}
1429+
auto socket = std::make_shared<TSocket>(std::move(hostname), fd, type, isClient, hostType,
1430+
readType, creationTime);
1431+
socket->_isCounted = isCounted;
13411432
socket->setHandler(std::move(handler));
13421433

13431434
return socket;

test/UnitTimeoutBase.hpp

+6
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,8 @@ inline UnitBase::TestResult UnitTimeoutBase1::testHttp(const size_t connectionLi
204204
sessions.clear();
205205
TST_LOG("Clearing Poller: " << testname);
206206
socketPollers.clear();
207+
// TCP Connection Count: Just an estimation, no locking on server side
208+
TST_LOG("TCP Connection Count: " << testname << ", " << Socket::getConnectionCount() << " / " << net::Defaults.maxTCPConnections);
207209
TST_LOG("Ending Test: " << testname);
208210
return TestResult::Ok;
209211
}
@@ -302,6 +304,8 @@ inline UnitBase::TestResult UnitTimeoutBase1::testWSPing(const size_t connection
302304
sessions.clear();
303305
TST_LOG("Clearing Poller: " << testname);
304306
socketPollers.clear();
307+
// TCP Connection Count: Just an estimation, no locking on server side
308+
TST_LOG("TCP Connection Count: " << testname << ", " << Socket::getConnectionCount() << " / " << net::Defaults.maxTCPConnections);
305309
TST_LOG("Ending Test: " << testname);
306310
return TestResult::Ok;
307311
}
@@ -397,6 +401,8 @@ inline UnitBase::TestResult UnitTimeoutBase1::testWSDChatPing(const size_t conne
397401
sessions.clear();
398402
TST_LOG("Clearing Poller: " << testname);
399403
socketPollers.clear();
404+
// TCP Connection Count: Just an estimation, no locking on server side
405+
TST_LOG("TCP Connection Count: " << testname << ", " << Socket::getConnectionCount() << " / " << net::Defaults.maxTCPConnections);
400406
TST_LOG("Ending Test: " << testname);
401407
return TestResult::Ok;
402408
}

0 commit comments

Comments
 (0)