diff --git a/package.json b/package.json index 5e10614c..38803915 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Calvin Montgomery", "name": "CyTube", "description": "Online media synchronizer and chat", - "version": "3.51.5", + "version": "3.51.6", "repository": { "url": "http://github.com/calzoneman/sync" }, diff --git a/src/io/ioserver.js b/src/io/ioserver.js index 9058f864..6a7a66c3 100644 --- a/src/io/ioserver.js +++ b/src/io/ioserver.js @@ -21,7 +21,19 @@ import http from 'http'; const LOGGER = require('@calzoneman/jsli')('ioserver'); -// WIP, not in use yet +const rateLimitExceeded = new Counter({ + name: 'cytube_socketio_rate_limited_total', + help: 'Number of socket.io connections rejected due to exceeding rate limit' +}); +const connLimitExceeded = new Counter({ + name: 'cytube_socketio_conn_limited_total', + help: 'Number of socket.io connections rejected due to exceeding conn limit' +}); +const authFailureCount = new Counter({ + name: 'cytube_socketio_auth_error_total', + help: 'Number of failed authentications from session middleware' +}); + class IOServer { constructor(options = { proxyTrustFn: proxyaddr.compile('127.0.0.1') @@ -65,6 +77,7 @@ class IOServer { const bucket = this.ipThrottle.get(socket.context.ipAddress); if (bucket.throttle()) { + rateLimitExceeded.inc(1); LOGGER.info('Rejecting %s - exceeded connection rate limit', socket.context.ipAddress); next(new Error('Rate limit exceeded')); @@ -74,6 +87,8 @@ class IOServer { next(); } + /* + TODO: see https://github.com/calzoneman/sync/issues/724 ipConnectionLimitMiddleware(socket, next) { const ip = socket.context.ipAddress; const count = this.ipCount.get(ip) || 0; @@ -84,12 +99,45 @@ class IOServer { } this.ipCount.set(ip, count + 1); + console.log(ip, this.ipCount.get(ip)); socket.once('disconnect', () => { + console.log('Disconnect event has fired for', socket.id); this.ipCount.set(ip, this.ipCount.get(ip) - 1); }); next(); } + */ + + checkIPLimit(socket) { + const ip = socket.context.ipAddress; + const count = this.ipCount.get(ip) || 0; + if (count >= Config.get('io.ip-connection-limit')) { + connLimitExceeded.inc(1); + LOGGER.info( + 'Rejecting %s - exceeded connection count limit', + ip + ); + socket.emit('kick', { + reason: 'Too many connections from your IP address' + }); + socket.disconnect(true); + return false; + } + + this.ipCount.set(ip, count + 1); + socket.once('disconnect', () => { + const newCount = (this.ipCount.get(ip) || 1) - 1; + + if (newCount === 0) { + this.ipCount.delete(ip); + } else { + this.ipCount.set(ip, newCount); + } + }); + + return true; + } // Parse cookies cookieParsingMiddleware(socket, next) { @@ -132,6 +180,7 @@ class IOServer { promises.push(verifySession(auth).then(user => { socket.context.user = Object.assign({}, user); }).catch(error => { + authFailureCount.inc(1); LOGGER.warn('Unable to verify session for %s - ignoring auth', socket.context.ipAddress); })); @@ -153,6 +202,12 @@ class IOServer { } handleConnection(socket) { + // TODO: move out of handleConnection if possible + // see: https://github.com/calzoneman/sync/issues/724 + if (!this.checkIPLimit(socket)) { + return; + } + LOGGER.info('Accepted socket from %s', socket.context.ipAddress); counters.add('socket.io:accept', 1); socket.once('disconnect', () => counters.add('socket.io:disconnect', 1)); @@ -176,7 +231,7 @@ class IOServer { io.use(this.ipProxyMiddleware.bind(this)); io.use(this.ipBanMiddleware.bind(this)); io.use(this.ipThrottleMiddleware.bind(this)); - io.use(this.ipConnectionLimitMiddleware.bind(this)); + //io.use(this.ipConnectionLimitMiddleware.bind(this)); io.use(this.cookieParsingMiddleware.bind(this)); io.use(this.ipSessionCookieMiddleware.bind(this)); io.use(this.authUserMiddleware.bind(this)); @@ -209,12 +264,12 @@ function patchSocketMetrics() { Socket.prototype.onevent = function patchedOnevent() { onevent.apply(this, arguments); - incomingEventCount.inc(1, new Date()); + incomingEventCount.inc(1); }; Socket.prototype.packet = function patchedPacket() { packet.apply(this, arguments); - outgoingPacketCount.inc(1, new Date()); + outgoingPacketCount.inc(1); }; } @@ -278,7 +333,7 @@ function emitMetrics(sock) { let closed = false; let transportName = sock.client.conn.transport.name; promSocketCount.inc({ transport: transportName }); - promSocketAccept.inc(1, new Date()); + promSocketAccept.inc(1); sock.client.conn.on('upgrade', newTransport => { try { @@ -298,7 +353,7 @@ function emitMetrics(sock) { try { closed = true; promSocketCount.dec({ transport: transportName }); - promSocketDisconnect.inc(1, new Date()); + promSocketDisconnect.inc(1); } catch (error) { LOGGER.error('Error emitting disconnect metrics for socket (ip=%s): %s', sock.context.ipAddress, error.stack); diff --git a/www/js/callbacks.js b/www/js/callbacks.js index 3329acea..02d05b61 100644 --- a/www/js/callbacks.js +++ b/www/js/callbacks.js @@ -37,7 +37,7 @@ Callbacks = { // Socket.IO error callback error: function (msg) { - window.SOCKET_ERROR_REASON = reason; + window.SOCKET_ERROR_REASON = msg; $("
") .addClass("server-msg-disconnect") .text("Unable to connect: " + msg)