sync/lib/io/ioserver.js

253 lines
6.8 KiB
JavaScript
Raw Normal View History

2013-12-12 23:09:49 +00:00
var sio = require("socket.io");
2014-08-19 05:46:30 +00:00
var cookieParser = require("cookie-parser")();
2013-12-12 23:09:49 +00:00
var Logger = require("../logger");
var db = require("../database");
var User = require("../user");
var Server = require("../server");
2014-01-22 23:11:26 +00:00
var Config = require("../config");
2013-12-12 23:09:49 +00:00
var $util = require("../utilities");
2014-05-21 02:30:14 +00:00
var Flags = require("../flags");
var Account = require("../account");
var typecheck = require("json-typecheck");
var net = require("net");
var util = require("../utilities");
var crypto = require("crypto");
var isTorExit = require("../tor").isTorExit;
2013-12-12 23:09:49 +00:00
var CONNECT_RATE = {
burst: 5,
sustained: 0.1
};
var ipThrottle = {};
// Keep track of number of connections per IP
var ipCount = {};
/**
* Called before an incoming socket.io connection is accepted.
*/
2014-08-20 03:57:28 +00:00
function handleAuth(socket, accept) {
var data = socket.request;
socket.user = false;
2013-12-12 23:09:49 +00:00
if (data.headers.cookie) {
2014-08-19 05:46:30 +00:00
cookieParser(data, null, function () {
var auth = data.cookies.auth;
db.users.verifyAuth(auth, function (err, user) {
if (!err) {
2014-08-20 03:57:28 +00:00
socket.user = {
2014-08-19 05:46:30 +00:00
name: user.name,
global_rank: user.global_rank
};
}
accept(null, true);
});
2013-12-12 23:09:49 +00:00
});
} else {
accept(null, true);
}
}
function throttleIP(sock) {
var ip = sock._realip;
2013-12-12 23:09:49 +00:00
if (!(ip in ipThrottle)) {
ipThrottle[ip] = $util.newRateLimiter();
}
if (ipThrottle[ip].throttle(CONNECT_RATE)) {
Logger.syslog.log("WARN: IP throttled: " + ip);
sock.emit("kick", {
reason: "Your IP address is connecting too quickly. Please "+
"wait 10 seconds before joining again."
});
return true;
2013-12-12 23:09:49 +00:00
}
return false;
}
function ipLimitReached(sock) {
var ip = sock._realip;
2013-12-12 23:09:49 +00:00
sock.on("disconnect", function () {
ipCount[ip]--;
if (ipCount[ip] === 0) {
/* Clear out unnecessary counters to save memory */
delete ipCount[ip];
}
2013-12-12 23:09:49 +00:00
});
if (!(ip in ipCount)) {
ipCount[ip] = 0;
}
ipCount[ip]++;
2014-01-22 23:11:26 +00:00
if (ipCount[ip] > Config.get("io.ip-connection-limit")) {
2013-12-12 23:09:49 +00:00
sock.emit("kick", {
reason: "Too many connections from your IP address"
});
2014-10-26 03:49:22 +00:00
sock.disconnect();
2013-12-12 23:09:49 +00:00
return;
}
}
2013-12-12 23:09:49 +00:00
function addTypecheckedFunctions(sock) {
2014-05-21 02:30:14 +00:00
sock.typecheckedOn = function (msg, template, cb) {
sock.on(msg, function (data) {
typecheck(data, template, function (err, data) {
if (err) {
sock.emit("errorMsg", {
msg: "Unexpected error for message " + msg + ": " + err.message
});
} else {
cb(data);
}
});
});
};
sock.typecheckedOnce = function (msg, template, cb) {
sock.once(msg, function (data) {
typecheck(data, template, function (err, data) {
if (err) {
sock.emit("errorMsg", {
msg: "Unexpected error for message " + msg + ": " + err.message
});
} else {
cb(data);
}
});
});
};
}
/**
* Called after a connection is accepted
*/
function handleConnection(sock) {
var ip = sock.client.conn.remoteAddress;
2014-08-20 17:09:38 +00:00
if (!ip) {
2014-08-20 17:11:46 +00:00
sock.emit("kick", {
reason: "Your IP address could not be determined from the socket connection. See https://github.com/Automattic/socket.io/issues/1737 for details"
2014-08-20 17:09:38 +00:00
});
return;
}
if (net.isIPv6(ip)) {
ip = util.expandIPv6(ip);
}
sock._realip = ip;
sock._displayip = $util.cloakIP(ip);
if (isTorExit(ip)) {
sock._isUsingTor = true;
}
var srv = Server.getServer();
2014-08-15 03:02:58 +00:00
if (throttleIP(sock)) {
return;
}
// Check for global ban on the IP
if (db.isGlobalIPBanned(ip)) {
Logger.syslog.log("Rejecting " + ip + " - global banned");
sock.emit("kick", { reason: "Your IP is globally banned." });
2014-10-26 03:49:22 +00:00
sock.disconnect();
return;
}
if (ipLimitReached(sock)) {
return;
}
Logger.syslog.log("Accepted socket from " + ip);
addTypecheckedFunctions(sock);
2014-05-21 02:30:14 +00:00
2013-12-12 23:09:49 +00:00
var user = new User(sock);
2014-08-20 03:57:28 +00:00
if (sock.user) {
2014-05-21 02:30:14 +00:00
user.setFlag(Flags.U_REGISTERED);
user.clearFlag(Flags.U_READY);
2014-08-20 03:57:28 +00:00
user.refreshAccount({ name: sock.user.name },
2014-05-21 02:30:14 +00:00
function (err, account) {
if (err) {
user.clearFlag(Flags.U_REGISTERED);
user.setFlag(Flags.U_READY);
return;
}
2014-05-21 02:30:14 +00:00
user.socket.emit("login", {
success: true,
name: user.getName(),
guest: false
});
db.recordVisit(ip, user.getName());
user.socket.emit("rank", user.account.effectiveRank);
user.setFlag(Flags.U_LOGGED_IN);
user.emit("login", account);
Logger.syslog.log(ip + " logged in as " + user.getName());
user.setFlag(Flags.U_READY);
2013-12-25 21:18:21 +00:00
});
2014-01-19 02:18:00 +00:00
} else {
user.socket.emit("rank", -1);
2014-05-21 02:30:14 +00:00
user.setFlag(Flags.U_READY);
2013-12-12 23:09:49 +00:00
}
}
module.exports = {
init: function (srv) {
2014-08-20 03:57:28 +00:00
var bound = {};
var io = sio.instance = sio();
io.use(handleAuth);
io.on("connection", handleConnection);
Config.get("listen").forEach(function (bind) {
if (!bind.io) {
return;
}
var id = bind.ip + ":" + bind.port;
2014-08-20 03:57:28 +00:00
if (id in bound) {
Logger.syslog.log("[WARN] Ignoring duplicate listen address " + id);
return;
}
2013-12-12 23:09:49 +00:00
if (id in srv.servers) {
2014-08-20 03:57:28 +00:00
io.attach(srv.servers[id]);
} else {
2015-01-06 15:54:14 +00:00
var server = require("http").createServer().listen(bind.port, bind.ip);
server.on("clientError", function (err, socket) {
console.error("clientError on " + id + " - " + err);
2015-01-06 15:54:14 +00:00
try {
socket.destroy();
} catch (e) {
}
});
io.attach(server);
}
2014-05-21 02:30:14 +00:00
2014-08-20 03:57:28 +00:00
bound[id] = null;
});
2013-12-12 23:09:49 +00:00
}
};
/* Clean out old rate limiters */
setInterval(function () {
for (var ip in ipThrottle) {
if (ipThrottle[ip].lastTime < Date.now() - 60 * 1000) {
var obj = ipThrottle[ip];
/* Not strictly necessary, but seems to help the GC out a bit */
for (var key in obj) {
delete obj[key];
}
delete ipThrottle[ip];
}
}
if (Config.get("aggressive-gc") && global && global.gc) {
global.gc();
}
}, 5 * 60 * 1000);