From 9c1216eb637b61606d3ea4695144e0c65fc579f2 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 19 Nov 2014 15:55:01 -0500 Subject: [PATCH] closes #2432 --- src/middleware/middleware.js | 3 ++- src/middleware/ratelimit.js | 35 +++++++++++++++++++++++++++++++++++ src/socket.io/index.js | 26 ++++++++++++++++---------- 3 files changed, 53 insertions(+), 11 deletions(-) create mode 100644 src/middleware/ratelimit.js diff --git a/src/middleware/middleware.js b/src/middleware/middleware.js index 89c954bccd..315d7cddf6 100644 --- a/src/middleware/middleware.js +++ b/src/middleware/middleware.js @@ -6,9 +6,11 @@ var app, }, async = require('async'), path = require('path'), + csrf = require('csurf'), winston = require('winston'), validator = require('validator'), nconf = require('nconf'), + plugins = require('./../plugins'), meta = require('./../meta'), translator = require('./../../public/src/translator'), @@ -18,7 +20,6 @@ var app, topics = require('./../topics'), messaging = require('../messaging'), ensureLoggedIn = require('connect-ensure-login'), - csrf = require('csurf'), controllers = { api: require('./../controllers/api') diff --git a/src/middleware/ratelimit.js b/src/middleware/ratelimit.js new file mode 100644 index 0000000000..a468c97662 --- /dev/null +++ b/src/middleware/ratelimit.js @@ -0,0 +1,35 @@ + + +'use strict'; +var winston = require('winston'); + +var ratelimit = {}; + +var allowedCallsPerSecond = 10; + + +ratelimit.isFlooding = function(socket) { + socket.callsPerSecond = socket.callsPerSecond || 0; + socket.elapsedTime = socket.elapsedTime || 0; + socket.lastCallTime = socket.lastCallTime || Date.now(); + + ++socket.callsPerSecond; + + var now = Date.now(); + socket.elapsedTime += now - socket.lastCallTime; + + if (socket.callsPerSecond > allowedCallsPerSecond && socket.elapsedTime < 1000) { + winston.warn('Flooding detected! Calls : ' + socket.callsPerSecond + ', Duration : ' + socket.elapsedTime); + return true; + } + + if (socket.elapsedTime >= 1000) { + socket.elapsedTime = 0; + socket.callsPerSecond = 0; + } + + socket.lastCallTime = now; + return false; +}; + +module.exports = ratelimit; \ No newline at end of file diff --git a/src/socket.io/index.js b/src/socket.io/index.js index c58ce1dc95..b13b584ba3 100644 --- a/src/socket.io/index.js +++ b/src/socket.io/index.js @@ -15,6 +15,7 @@ var SocketIO = require('socket.io'), topics = require('../topics'), logger = require('../logger'), meta = require('../meta'), + ratelimit = require('../middleware/ratelimit'), Sockets = {}, Namespaces = {}; @@ -195,16 +196,13 @@ Sockets.init = function(server) { }); socket.on('*', function(payload, callback) { - function callMethod(method) { - method.call(null, socket, payload.args.length ? payload.args[0] : null, function(err, result) { - if (callback) { - callback(err?{message:err.message}:null, result); - } - }); + if (!payload.name) { + return winston.warn('[socket.io] Empty method name'); } - if(!payload.name) { - return winston.warn('[socket.io] Empty method name'); + if (ratelimit.isFlooding(socket)) { + winston.warn('[socket.io] Too many emits! Disconnecting ' + socket.uid); + return socket.disconnect(); } var parts = payload.name.toString().split('.'), @@ -226,15 +224,23 @@ Sockets.init = function(server) { if (Namespaces[namespace].before) { Namespaces[namespace].before(socket, payload.name, function() { - callMethod(methodToCall); + callMethod(methodToCall, socket, payload, callback); }); } else { - callMethod(methodToCall); + callMethod(methodToCall, socket, payload, callback); } }); }); }; +function callMethod(method, socket, payload, callback) { + method.call(null, socket, payload.args.length ? payload.args[0] : null, function(err, result) { + if (callback) { + callback(err ? {message: err.message} : null, result); + } + }); +} + Sockets.logoutUser = function(uid) { Sockets.getUserSockets(uid).forEach(function(socket) { if (socket.handshake && socket.handshake.signedCookies && socket.handshake.signedCookies['express.sid']) {