From 08cb690822aaa6613af36c9aaf8091a936755e0c Mon Sep 17 00:00:00 2001 From: Logan Nguyen Date: Thu, 18 Apr 2024 12:25:30 -0400 Subject: [PATCH] fix: added ability to send back to clients the reason on closing at max connection limit (#2362) (#2363) * fix: removed extra requestIdPrefix Signed-off-by: Logan Nguyen * fix: aded ability to send back to client when closing connection in limiter Signed-off-by: Logan Nguyen --------- Signed-off-by: Logan Nguyen --- .../ws-server/src/utils/connectionLimiter.ts | 37 +++++++++++++++---- packages/ws-server/src/webSocketServer.ts | 9 ++--- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/packages/ws-server/src/utils/connectionLimiter.ts b/packages/ws-server/src/utils/connectionLimiter.ts index a86594f396..8906df8f13 100644 --- a/packages/ws-server/src/utils/connectionLimiter.ts +++ b/packages/ws-server/src/utils/connectionLimiter.ts @@ -115,24 +115,38 @@ export default class ConnectionLimiter { public applyLimits(ctx) { // Limit total connections - if (this.connectedClients > parseInt(process.env.WS_CONNECTION_LIMIT || '10')) { + const MAX_CONNECTION_LIMIT = process.env.WS_CONNECTION_LIMIT || '10'; + if (this.connectedClients > parseInt(MAX_CONNECTION_LIMIT)) { this.logger.info( - `Closing connection ${ctx.websocket.id} due to exceeded maximum connections (${process.env.WS_CONNECTION_LIMIT})`, + `Closing connection ${ctx.websocket.id} due to exceeded maximum connections (max_con=${MAX_CONNECTION_LIMIT})`, ); this.connectionLimitCounter.inc(); + ctx.websocket.send( + JSON.stringify({ + jsonrpc: '2.0', + error: `Closing current connection due to exceeded maximum connections (max_con=${MAX_CONNECTION_LIMIT})`, + id: '1', + }), + ); ctx.websocket.close(CONNECTION_LIMIT_EXCEEDED.code, CONNECTION_LIMIT_EXCEEDED.message); return; } - const { ip } = ctx.request; - // Limit connections from a single IP address - const limitPerIp = parseInt(process.env.WS_CONNECTION_LIMIT_PER_IP || '10'); - if (this.clientIps[ip] && this.clientIps[ip] > limitPerIp) { + const { ip } = ctx.request; + const MAX_CONNECTION_LIMIT_PER_IP = process.env.WS_CONNECTION_LIMIT_PER_IP || '10'; + if (this.clientIps[ip] && this.clientIps[ip] > parseInt(MAX_CONNECTION_LIMIT_PER_IP)) { this.logger.info( - `Closing connection ${ctx.websocket.id} due to exceeded maximum connections from a single IP (${this.clientIps[ip]}) for address ${ip}`, + `Closing connection ${ctx.websocket.id} due to exceeded maximum connections from a single IP: address ${ip} - ${this.clientIps[ip]} connections. (max_con=${MAX_CONNECTION_LIMIT_PER_IP})`, ); this.ipConnectionLimitCounter.labels(ip).inc(); + ctx.websocket.send( + JSON.stringify({ + jsonrpc: '2.0', + error: `Closing current connection due to exceeded maximum connections from a single IP: address ${ip} - ${this.clientIps[ip]} connections. (max_con=${MAX_CONNECTION_LIMIT_PER_IP})`, + id: '1', + }), + ); ctx.websocket.close(CONNECTION_IP_LIMIT_EXCEEDED.code, CONNECTION_IP_LIMIT_EXCEEDED.message); return; } @@ -159,9 +173,16 @@ export default class ConnectionLimiter { websocket.inactivityTTL = setTimeout(() => { if (websocket.readyState !== 3) { // 3 = CLOSED, Avoid closing already closed connections - this.logger.debug(`Closing connection ${websocket.id} due to reaching TTL of ${maxInactivityTTL}ms`); + this.logger.debug(`Closing connection ${websocket.id} due to reaching TTL (${maxInactivityTTL}ms)`); try { this.inactivityTTLCounter.inc(); + websocket.send( + JSON.stringify({ + jsonrpc: '2.0', + error: `Closing current connection due to reaching TTL (${maxInactivityTTL}ms)`, + id: '1', + }), + ); websocket.close(TTL_EXPIRED.code, TTL_EXPIRED.message); } catch (e) { this.logger.error(`${websocket.id}: ${e}`); diff --git a/packages/ws-server/src/webSocketServer.ts b/packages/ws-server/src/webSocketServer.ts index e7f2d0769d..88eeb8bb52 100644 --- a/packages/ws-server/src/webSocketServer.ts +++ b/packages/ws-server/src/webSocketServer.ts @@ -92,15 +92,15 @@ app.ws.use(async (ctx) => { ctx.websocket.id = relay.subs()?.generateId(); ctx.websocket.limiter = limiter; const connectionIdPrefix = formatIdMessage('Connection ID', ctx.websocket.id); - const connectionRequestIdPrefix = formatIdMessage('Request ID', uuid()); + const requestIdPrefix = formatIdMessage('Request ID', uuid()); logger.info( - `${connectionIdPrefix} ${connectionRequestIdPrefix} New connection established. Current active connections: ${ctx.app.server._connections}`, + `${connectionIdPrefix} ${requestIdPrefix} New connection established. Current active connections: ${ctx.app.server._connections}`, ); // Close event handle ctx.websocket.on('close', async (code, message) => { logger.info( - `${connectionIdPrefix} ${connectionRequestIdPrefix} Closing connection ${ctx.websocket.id} | code: ${code}, message: ${message}`, + `${connectionIdPrefix} ${requestIdPrefix} Closing connection ${ctx.websocket.id} | code: ${code}, message: ${message}`, ); await handleConnectionClose(ctx, relay, limiter); }); @@ -116,9 +116,6 @@ app.ws.use(async (ctx) => { // Reset the TTL timer for inactivity upon receiving a message from the client limiter.resetInactivityTTLTimer(ctx.websocket); - // Format ID prefixes for logging purposes - const requestIdPrefix = formatIdMessage('Request ID', uuid()); - // parse the received message from the client into a JSON object let request; try {