Workaround for #724

This commit is contained in:
Calvin Montgomery 2017-12-06 22:09:13 -08:00
parent 60f77d4eb9
commit 9886f648f2
3 changed files with 63 additions and 8 deletions

View File

@ -2,7 +2,7 @@
"author": "Calvin Montgomery", "author": "Calvin Montgomery",
"name": "CyTube", "name": "CyTube",
"description": "Online media synchronizer and chat", "description": "Online media synchronizer and chat",
"version": "3.51.5", "version": "3.51.6",
"repository": { "repository": {
"url": "http://github.com/calzoneman/sync" "url": "http://github.com/calzoneman/sync"
}, },

View File

@ -21,7 +21,19 @@ import http from 'http';
const LOGGER = require('@calzoneman/jsli')('ioserver'); 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 { class IOServer {
constructor(options = { constructor(options = {
proxyTrustFn: proxyaddr.compile('127.0.0.1') proxyTrustFn: proxyaddr.compile('127.0.0.1')
@ -65,6 +77,7 @@ class IOServer {
const bucket = this.ipThrottle.get(socket.context.ipAddress); const bucket = this.ipThrottle.get(socket.context.ipAddress);
if (bucket.throttle()) { if (bucket.throttle()) {
rateLimitExceeded.inc(1);
LOGGER.info('Rejecting %s - exceeded connection rate limit', LOGGER.info('Rejecting %s - exceeded connection rate limit',
socket.context.ipAddress); socket.context.ipAddress);
next(new Error('Rate limit exceeded')); next(new Error('Rate limit exceeded'));
@ -74,6 +87,8 @@ class IOServer {
next(); next();
} }
/*
TODO: see https://github.com/calzoneman/sync/issues/724
ipConnectionLimitMiddleware(socket, next) { ipConnectionLimitMiddleware(socket, next) {
const ip = socket.context.ipAddress; const ip = socket.context.ipAddress;
const count = this.ipCount.get(ip) || 0; const count = this.ipCount.get(ip) || 0;
@ -84,12 +99,45 @@ class IOServer {
} }
this.ipCount.set(ip, count + 1); this.ipCount.set(ip, count + 1);
console.log(ip, this.ipCount.get(ip));
socket.once('disconnect', () => { socket.once('disconnect', () => {
console.log('Disconnect event has fired for', socket.id);
this.ipCount.set(ip, this.ipCount.get(ip) - 1); this.ipCount.set(ip, this.ipCount.get(ip) - 1);
}); });
next(); 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 // Parse cookies
cookieParsingMiddleware(socket, next) { cookieParsingMiddleware(socket, next) {
@ -132,6 +180,7 @@ class IOServer {
promises.push(verifySession(auth).then(user => { promises.push(verifySession(auth).then(user => {
socket.context.user = Object.assign({}, user); socket.context.user = Object.assign({}, user);
}).catch(error => { }).catch(error => {
authFailureCount.inc(1);
LOGGER.warn('Unable to verify session for %s - ignoring auth', LOGGER.warn('Unable to verify session for %s - ignoring auth',
socket.context.ipAddress); socket.context.ipAddress);
})); }));
@ -153,6 +202,12 @@ class IOServer {
} }
handleConnection(socket) { 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); LOGGER.info('Accepted socket from %s', socket.context.ipAddress);
counters.add('socket.io:accept', 1); counters.add('socket.io:accept', 1);
socket.once('disconnect', () => counters.add('socket.io:disconnect', 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.ipProxyMiddleware.bind(this));
io.use(this.ipBanMiddleware.bind(this)); io.use(this.ipBanMiddleware.bind(this));
io.use(this.ipThrottleMiddleware.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.cookieParsingMiddleware.bind(this));
io.use(this.ipSessionCookieMiddleware.bind(this)); io.use(this.ipSessionCookieMiddleware.bind(this));
io.use(this.authUserMiddleware.bind(this)); io.use(this.authUserMiddleware.bind(this));
@ -209,12 +264,12 @@ function patchSocketMetrics() {
Socket.prototype.onevent = function patchedOnevent() { Socket.prototype.onevent = function patchedOnevent() {
onevent.apply(this, arguments); onevent.apply(this, arguments);
incomingEventCount.inc(1, new Date()); incomingEventCount.inc(1);
}; };
Socket.prototype.packet = function patchedPacket() { Socket.prototype.packet = function patchedPacket() {
packet.apply(this, arguments); packet.apply(this, arguments);
outgoingPacketCount.inc(1, new Date()); outgoingPacketCount.inc(1);
}; };
} }
@ -278,7 +333,7 @@ function emitMetrics(sock) {
let closed = false; let closed = false;
let transportName = sock.client.conn.transport.name; let transportName = sock.client.conn.transport.name;
promSocketCount.inc({ transport: transportName }); promSocketCount.inc({ transport: transportName });
promSocketAccept.inc(1, new Date()); promSocketAccept.inc(1);
sock.client.conn.on('upgrade', newTransport => { sock.client.conn.on('upgrade', newTransport => {
try { try {
@ -298,7 +353,7 @@ function emitMetrics(sock) {
try { try {
closed = true; closed = true;
promSocketCount.dec({ transport: transportName }); promSocketCount.dec({ transport: transportName });
promSocketDisconnect.inc(1, new Date()); promSocketDisconnect.inc(1);
} catch (error) { } catch (error) {
LOGGER.error('Error emitting disconnect metrics for socket (ip=%s): %s', LOGGER.error('Error emitting disconnect metrics for socket (ip=%s): %s',
sock.context.ipAddress, error.stack); sock.context.ipAddress, error.stack);

View File

@ -37,7 +37,7 @@ Callbacks = {
// Socket.IO error callback // Socket.IO error callback
error: function (msg) { error: function (msg) {
window.SOCKET_ERROR_REASON = reason; window.SOCKET_ERROR_REASON = msg;
$("<div/>") $("<div/>")
.addClass("server-msg-disconnect") .addClass("server-msg-disconnect")
.text("Unable to connect: " + msg) .text("Unable to connect: " + msg)