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
|
||||
* Connecting to multiple daemons for redundancy
|
||||
* 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
|
||||
* Transaction messages support
|
||||
* Vardiff (variable difficulty / share limiter)
|
||||
@ -49,7 +51,6 @@ Features
|
||||
|
||||
#### To do
|
||||
* Statistics module
|
||||
* Auto-banning flooders
|
||||
|
||||
|
||||
Requirements
|
||||
@ -87,8 +88,20 @@ var pool = Stratum.createPool({
|
||||
|
||||
//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
|
||||
|
||||
/* 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
|
||||
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. */
|
||||
|
||||
17
lib/pool.js
17
lib/pool.js
@ -101,7 +101,7 @@ var pool = module.exports = function pool(options, authorizeFn){
|
||||
}
|
||||
else{
|
||||
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(){
|
||||
_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(){
|
||||
emitLog('system','Stratum server started on port(s): ' + Object.keys(options.ports).join(', '));
|
||||
_this.emit('started');
|
||||
@ -365,15 +365,18 @@ var pool = module.exports = function pool(options, authorizeFn){
|
||||
resultCallback(result.error, result.result ? true : null);
|
||||
|
||||
}).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() {
|
||||
emitWarningLog('client', client.workerName+" has somehow had a socket error");
|
||||
emitWarningLog('client', client.workerName + " has somehow had a socket error");
|
||||
}).on('socketDisconnect', function() {
|
||||
emitLog('client', "Client '"+client.workerName+"' disconnected!");
|
||||
emitLog('client', "Client '" + client.workerName + "' disconnected!");
|
||||
}).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(){
|
||||
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);
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.relinquishMiners = function(filterFn, resultCback) {
|
||||
var origStratumClients = this.stratumServer.getStratumClients();
|
||||
|
||||
@ -28,10 +28,28 @@ var StratumClient = function(options){
|
||||
//private members
|
||||
this.socket = options.socket;
|
||||
|
||||
var banning = options.banning;
|
||||
|
||||
var _this = this;
|
||||
|
||||
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(){
|
||||
setupSocket();
|
||||
@ -120,6 +138,7 @@ var StratumClient = function(options){
|
||||
result: null,
|
||||
error : [24, "unauthorized worker", null]
|
||||
});
|
||||
considerBan(false);
|
||||
return;
|
||||
}
|
||||
if (!_this.extraNonce1){
|
||||
@ -128,6 +147,7 @@ var StratumClient = function(options){
|
||||
result: null,
|
||||
error : [25, "not subscribed", null]
|
||||
});
|
||||
considerBan(false);
|
||||
return;
|
||||
}
|
||||
_this.emit('submit',
|
||||
@ -139,6 +159,7 @@ var StratumClient = function(options){
|
||||
nonce : message.params[4]
|
||||
},
|
||||
function(error, result){
|
||||
considerBan(result);
|
||||
sendJson({
|
||||
id : message.id,
|
||||
result : result,
|
||||
@ -226,6 +247,12 @@ var StratumClient = function(options){
|
||||
};
|
||||
|
||||
this.sendMiningJob = function(jobParams){
|
||||
|
||||
if (Date.now() - _this.lastActivity > options.socketTimeout){
|
||||
_this.socket.end();
|
||||
return;
|
||||
}
|
||||
|
||||
if (pendingDifficulty !== null){
|
||||
var result = _this.sendDifficulty(pendingDifficulty);
|
||||
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.
|
||||
* - '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
|
||||
|
||||
var connectionTimeoutSeconds = connectionTimeout * 1000;
|
||||
var socketTimeout = connectionTimeout * 1000;
|
||||
var bannedMS = banning.time * 1000;
|
||||
|
||||
var _this = this;
|
||||
var stratumClients = {};
|
||||
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) {
|
||||
socket.setKeepAlive(true);
|
||||
var subscriptionId = subscriptionCounter.next();
|
||||
@ -275,7 +314,9 @@ var StratumServer = exports.Server = function StratumServer(ports, connectionTim
|
||||
{
|
||||
subscriptionId : subscriptionId,
|
||||
socket : socket,
|
||||
authorizeFn : authorizeFn
|
||||
authorizeFn : authorizeFn,
|
||||
banning : banning,
|
||||
socketTimeout : socketTimeout
|
||||
}
|
||||
);
|
||||
stratumClients[subscriptionId] = client;
|
||||
@ -283,6 +324,8 @@ var StratumServer = exports.Server = function StratumServer(ports, connectionTim
|
||||
client.on('socketDisconnect', function() {
|
||||
_this.removeStratumClientBySubId(subscriptionId);
|
||||
_this.emit('client.disconnected', client);
|
||||
}).on('ban', function(ipAddress){
|
||||
_this.banIP(ipAddress);
|
||||
});
|
||||
return subscriptionId;
|
||||
};
|
||||
@ -291,6 +334,18 @@ var StratumServer = exports.Server = function StratumServer(ports, connectionTim
|
||||
|
||||
Object.keys(ports).forEach(function(port){
|
||||
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);
|
||||
}).listen(parseInt(port), function(){
|
||||
_this.emit('started');
|
||||
@ -302,16 +357,16 @@ var StratumServer = exports.Server = function StratumServer(ports, connectionTim
|
||||
|
||||
//public members
|
||||
|
||||
this.banIP = function(ipAddress){
|
||||
bannedIPs[ipAddress] = Date.now();
|
||||
};
|
||||
|
||||
this.broadcastMiningJobs = function(jobParams) {
|
||||
for (var clientId in stratumClients) {
|
||||
// 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];
|
||||
if (typeof(client) !== 'undefined') {
|
||||
if (Date.now() - client.lastActivity > connectionTimeoutSeconds){
|
||||
client.socket.end();
|
||||
return;
|
||||
}
|
||||
client.sendMiningJob(jobParams);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user