Added worker banning feature and updated readme
This commit is contained in:
parent
56e1905bb3
commit
43aa152c85
15
README.md
15
README.md
@ -31,6 +31,8 @@ Features
|
|||||||
* Optimized generation transaction building
|
* Optimized generation transaction building
|
||||||
* Connecting to multiple daemons for redundancy
|
* Connecting to multiple daemons for redundancy
|
||||||
* Process share submissions
|
* Process share submissions
|
||||||
|
* Session managing for purging DDoS/flood initiated fake workers
|
||||||
|
* Auto ban IPs that are flooding with invalid shares
|
||||||
* __POW__ (proof-of-work) & __POS__ (proof-of-stake) support
|
* __POW__ (proof-of-work) & __POS__ (proof-of-stake) support
|
||||||
* Transaction messages support
|
* Transaction messages support
|
||||||
* Vardiff (variable difficulty / share limiter)
|
* Vardiff (variable difficulty / share limiter)
|
||||||
@ -49,7 +51,6 @@ Features
|
|||||||
|
|
||||||
#### To do
|
#### To do
|
||||||
* Statistics module
|
* Statistics module
|
||||||
* Auto-banning flooders
|
|
||||||
|
|
||||||
|
|
||||||
Requirements
|
Requirements
|
||||||
@ -87,8 +88,20 @@ var pool = Stratum.createPool({
|
|||||||
|
|
||||||
//instanceId: 37, //Recommend not using this because a crypto-random one will be generated
|
//instanceId: 37, //Recommend not using this because a crypto-random one will be generated
|
||||||
|
|
||||||
|
/* Some attackers will create thousands of workers that use up all available socket connections,
|
||||||
|
usually the workers are zombies and don't submit shares after connecting. This features
|
||||||
|
detects those and disconnects them */
|
||||||
"connectionTimeout": 120, //Remove workers that haven't been in contact for this many seconds
|
"connectionTimeout": 120, //Remove workers that haven't been in contact for this many seconds
|
||||||
|
|
||||||
|
/* If a worker is submitting a good deal of invalid shares we can temporarily ban them to
|
||||||
|
reduce system/network load. Also useful to fight against flooding attacks. */
|
||||||
|
"banning": {
|
||||||
|
"enabled": true,
|
||||||
|
"time": 600, //How many seconds to ban worker for
|
||||||
|
"invalidPercent": 50, //What percent of invalid shares triggers ban
|
||||||
|
"checkThreshold": 500 //Check invalid percent when this many shares have been submitted
|
||||||
|
},
|
||||||
|
|
||||||
/* Each pool can have as many ports for your miners to connect to as you wish. Each port can
|
/* Each pool can have as many ports for your miners to connect to as you wish. Each port can
|
||||||
be configured to use its own pool difficulty and variable difficulty settings. varDiff is
|
be configured to use its own pool difficulty and variable difficulty settings. varDiff is
|
||||||
optional and will only be used for the ports you configure it for. */
|
optional and will only be used for the ports you configure it for. */
|
||||||
|
|||||||
17
lib/pool.js
17
lib/pool.js
@ -101,7 +101,7 @@ var pool = module.exports = function pool(options, authorizeFn){
|
|||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
rpcCommand = 'getblocktemplate';
|
rpcCommand = 'getblocktemplate';
|
||||||
rcpArgs = [{'mode': 'submit', 'data': blockHex}];
|
rpcArgs = [{'mode': 'submit', 'data': blockHex}];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -328,7 +328,7 @@ var pool = module.exports = function pool(options, authorizeFn){
|
|||||||
|
|
||||||
|
|
||||||
function StartStratumServer(){
|
function StartStratumServer(){
|
||||||
_this.stratumServer = new stratum.Server(options.ports, options.connectionTimeout, authorizeFn);
|
_this.stratumServer = new stratum.Server(options.ports, options.connectionTimeout, options.banning, authorizeFn);
|
||||||
_this.stratumServer.on('started', function(){
|
_this.stratumServer.on('started', function(){
|
||||||
emitLog('system','Stratum server started on port(s): ' + Object.keys(options.ports).join(', '));
|
emitLog('system','Stratum server started on port(s): ' + Object.keys(options.ports).join(', '));
|
||||||
_this.emit('started');
|
_this.emit('started');
|
||||||
@ -365,15 +365,18 @@ var pool = module.exports = function pool(options, authorizeFn){
|
|||||||
resultCallback(result.error, result.result ? true : null);
|
resultCallback(result.error, result.result ? true : null);
|
||||||
|
|
||||||
}).on('malformedMessage', function (message) {
|
}).on('malformedMessage', function (message) {
|
||||||
emitWarningLog('client', client.workerName+" has sent us a malformed message: "+message);
|
emitWarningLog('client', client.workerName + " has sent us a malformed message: " + message);
|
||||||
}).on('socketError', function() {
|
}).on('socketError', function() {
|
||||||
emitWarningLog('client', client.workerName+" has somehow had a socket error");
|
emitWarningLog('client', client.workerName + " has somehow had a socket error");
|
||||||
}).on('socketDisconnect', function() {
|
}).on('socketDisconnect', function() {
|
||||||
emitLog('client', "Client '"+client.workerName+"' disconnected!");
|
emitLog('client', "Client '" + client.workerName + "' disconnected!");
|
||||||
}).on('unknownStratumMethod', function(fullMessage) {
|
}).on('unknownStratumMethod', function(fullMessage) {
|
||||||
emitLog('client', "Client '"+client.workerName+"' has sent us an unknown stratum method: "+fullMessage.method);
|
emitLog('client', "Client '" + client.workerName + "' has sent us an unknown stratum method: " + fullMessage.method);
|
||||||
}).on('socketFlooded', function(){
|
}).on('socketFlooded', function(){
|
||||||
emitWarningLog('client', 'Detected socket flooding and purged buffer');
|
emitWarningLog('client', 'Detected socket flooding and purged buffer');
|
||||||
|
}).on('ban', function(ipAddress){
|
||||||
|
_this.emit('banIP', ipAddress);
|
||||||
|
emitWarningLog('client', 'banned IP ' + ipAddress);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -441,7 +444,7 @@ var pool = module.exports = function pool(options, authorizeFn){
|
|||||||
emitErrorLog('system', 'Block notify error getting block template for ' + options.coin.name);
|
emitErrorLog('system', 'Block notify error getting block template for ' + options.coin.name);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
this.relinquishMiners = function(filterFn, resultCback) {
|
this.relinquishMiners = function(filterFn, resultCback) {
|
||||||
var origStratumClients = this.stratumServer.getStratumClients();
|
var origStratumClients = this.stratumServer.getStratumClients();
|
||||||
|
|||||||
@ -28,10 +28,28 @@ var StratumClient = function(options){
|
|||||||
//private members
|
//private members
|
||||||
this.socket = options.socket;
|
this.socket = options.socket;
|
||||||
|
|
||||||
|
var banning = options.banning;
|
||||||
|
|
||||||
var _this = this;
|
var _this = this;
|
||||||
|
|
||||||
this.lastActivity = Date.now();
|
this.lastActivity = Date.now();
|
||||||
|
|
||||||
|
this.shares = {valid: 0, invalid: 0};
|
||||||
|
|
||||||
|
var considerBan = !banning.enabled ? function(){} : function(shareValid){
|
||||||
|
if (shareValid === true) _this.shares.valid++;
|
||||||
|
else _this.shares.invalid++;
|
||||||
|
var totalShares = _this.shares.valid + _this.shares.invalid;
|
||||||
|
if (totalShares >= banning.checkThreshold){
|
||||||
|
var percentBad = (_this.shares.invalid / totalShares) * 100;
|
||||||
|
if (percentBad >= banning.invalidPercent){
|
||||||
|
_this.emit('ban', _this.socket.remoteAddress);
|
||||||
|
_this.socket.end();
|
||||||
|
}
|
||||||
|
else //reset shares
|
||||||
|
this.shares = {valid: 0, invalid: 0};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
(function init(){
|
(function init(){
|
||||||
setupSocket();
|
setupSocket();
|
||||||
@ -120,6 +138,7 @@ var StratumClient = function(options){
|
|||||||
result: null,
|
result: null,
|
||||||
error : [24, "unauthorized worker", null]
|
error : [24, "unauthorized worker", null]
|
||||||
});
|
});
|
||||||
|
considerBan(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!_this.extraNonce1){
|
if (!_this.extraNonce1){
|
||||||
@ -128,6 +147,7 @@ var StratumClient = function(options){
|
|||||||
result: null,
|
result: null,
|
||||||
error : [25, "not subscribed", null]
|
error : [25, "not subscribed", null]
|
||||||
});
|
});
|
||||||
|
considerBan(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_this.emit('submit',
|
_this.emit('submit',
|
||||||
@ -139,6 +159,7 @@ var StratumClient = function(options){
|
|||||||
nonce : message.params[4]
|
nonce : message.params[4]
|
||||||
},
|
},
|
||||||
function(error, result){
|
function(error, result){
|
||||||
|
considerBan(result);
|
||||||
sendJson({
|
sendJson({
|
||||||
id : message.id,
|
id : message.id,
|
||||||
result : result,
|
result : result,
|
||||||
@ -226,6 +247,12 @@ var StratumClient = function(options){
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.sendMiningJob = function(jobParams){
|
this.sendMiningJob = function(jobParams){
|
||||||
|
|
||||||
|
if (Date.now() - _this.lastActivity > options.socketTimeout){
|
||||||
|
_this.socket.end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (pendingDifficulty !== null){
|
if (pendingDifficulty !== null){
|
||||||
var result = _this.sendDifficulty(pendingDifficulty);
|
var result = _this.sendDifficulty(pendingDifficulty);
|
||||||
pendingDifficulty = null;
|
pendingDifficulty = null;
|
||||||
@ -258,16 +285,28 @@ StratumClient.prototype.__proto__ = events.EventEmitter.prototype;
|
|||||||
* - 'client.disconnected'(StratumClientInstance) - when a miner disconnects. Be aware that the socket cannot be used anymore.
|
* - 'client.disconnected'(StratumClientInstance) - when a miner disconnects. Be aware that the socket cannot be used anymore.
|
||||||
* - 'started' - when the server is up and running
|
* - 'started' - when the server is up and running
|
||||||
**/
|
**/
|
||||||
var StratumServer = exports.Server = function StratumServer(ports, connectionTimeout, authorizeFn){
|
var StratumServer = exports.Server = function StratumServer(ports, connectionTimeout, banning, authorizeFn){
|
||||||
|
|
||||||
//private members
|
//private members
|
||||||
|
|
||||||
var connectionTimeoutSeconds = connectionTimeout * 1000;
|
var socketTimeout = connectionTimeout * 1000;
|
||||||
|
var bannedMS = banning.time * 1000;
|
||||||
|
|
||||||
var _this = this;
|
var _this = this;
|
||||||
var stratumClients = {};
|
var stratumClients = {};
|
||||||
var subscriptionCounter = SubscriptionCounter();
|
var subscriptionCounter = SubscriptionCounter();
|
||||||
|
|
||||||
|
var bannedIPs = {};
|
||||||
|
|
||||||
|
//Every 5 minutes look through bannedIPs for old bans and remove them in order to prevent a memory leak
|
||||||
|
var purgeOldBans = !banning.enabled ? null : setInterval(function(){
|
||||||
|
for (ip in bannedIPs){
|
||||||
|
var banTime = bannedIPs[ip];
|
||||||
|
if (Date.now() - banTime > banning.time)
|
||||||
|
delete bannedIPs[ip];
|
||||||
|
}
|
||||||
|
}, 1000 * 60 * 5); //5 minutes
|
||||||
|
|
||||||
var handleNewClient = function (socket) {
|
var handleNewClient = function (socket) {
|
||||||
socket.setKeepAlive(true);
|
socket.setKeepAlive(true);
|
||||||
var subscriptionId = subscriptionCounter.next();
|
var subscriptionId = subscriptionCounter.next();
|
||||||
@ -275,7 +314,9 @@ var StratumServer = exports.Server = function StratumServer(ports, connectionTim
|
|||||||
{
|
{
|
||||||
subscriptionId : subscriptionId,
|
subscriptionId : subscriptionId,
|
||||||
socket : socket,
|
socket : socket,
|
||||||
authorizeFn : authorizeFn
|
authorizeFn : authorizeFn,
|
||||||
|
banning : banning,
|
||||||
|
socketTimeout : socketTimeout
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
stratumClients[subscriptionId] = client;
|
stratumClients[subscriptionId] = client;
|
||||||
@ -283,6 +324,8 @@ var StratumServer = exports.Server = function StratumServer(ports, connectionTim
|
|||||||
client.on('socketDisconnect', function() {
|
client.on('socketDisconnect', function() {
|
||||||
_this.removeStratumClientBySubId(subscriptionId);
|
_this.removeStratumClientBySubId(subscriptionId);
|
||||||
_this.emit('client.disconnected', client);
|
_this.emit('client.disconnected', client);
|
||||||
|
}).on('ban', function(ipAddress){
|
||||||
|
_this.banIP(ipAddress);
|
||||||
});
|
});
|
||||||
return subscriptionId;
|
return subscriptionId;
|
||||||
};
|
};
|
||||||
@ -291,6 +334,18 @@ var StratumServer = exports.Server = function StratumServer(ports, connectionTim
|
|||||||
|
|
||||||
Object.keys(ports).forEach(function(port){
|
Object.keys(ports).forEach(function(port){
|
||||||
net.createServer({allowHalfOpen: true}, function(socket){
|
net.createServer({allowHalfOpen: true}, function(socket){
|
||||||
|
|
||||||
|
if (banning.enabled && socket.remoteAddress in bannedIPs){
|
||||||
|
var bannedTime = bannedIPs[socket.remoteAddress];
|
||||||
|
if ((Date.now() - bannedTime) < bannedMS){
|
||||||
|
socket.end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
delete bannedIPs[socket.remoteAddress];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleNewClient(socket);
|
handleNewClient(socket);
|
||||||
}).listen(parseInt(port), function(){
|
}).listen(parseInt(port), function(){
|
||||||
_this.emit('started');
|
_this.emit('started');
|
||||||
@ -302,16 +357,16 @@ var StratumServer = exports.Server = function StratumServer(ports, connectionTim
|
|||||||
|
|
||||||
//public members
|
//public members
|
||||||
|
|
||||||
|
this.banIP = function(ipAddress){
|
||||||
|
bannedIPs[ipAddress] = Date.now();
|
||||||
|
};
|
||||||
|
|
||||||
this.broadcastMiningJobs = function(jobParams) {
|
this.broadcastMiningJobs = function(jobParams) {
|
||||||
for (var clientId in stratumClients) {
|
for (var clientId in stratumClients) {
|
||||||
// if a client gets disconnected WHILE doing this loop a crash might happen.
|
// if a client gets disconnected WHILE doing this loop a crash might happen.
|
||||||
// 'm not sure if that can ever happn but an if here doesn't hurt!
|
// 'm not sure if that can ever happen but an if here doesn't hurt!
|
||||||
var client = stratumClients[clientId];
|
var client = stratumClients[clientId];
|
||||||
if (typeof(client) !== 'undefined') {
|
if (typeof(client) !== 'undefined') {
|
||||||
if (Date.now() - client.lastActivity > connectionTimeoutSeconds){
|
|
||||||
client.socket.end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
client.sendMiningJob(jobParams);
|
client.sendMiningJob(jobParams);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user