Skip to content

Commit

Permalink
Merge pull request #1858 from OriginTrail/v6/feature/libp2p-spam-blac…
Browse files Browse the repository at this point in the history
…klist

add blacklist and spam detection to libp2p service
  • Loading branch information
zeroxbt authored Mar 30, 2022
2 parents 14c6251 + 55a3714 commit 7a584a6
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 46 deletions.
69 changes: 58 additions & 11 deletions external/libp2p-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,7 @@ class Libp2pService {

initializationObject.peerId = this.config.peerId;
this.workerPool = this.config.workerPool;
this.limiter = new InMemoryRateLimiter({
interval: constants.NETWORK_API_RATE_LIMIT_TIME_WINDOW_MILLS,
maxInInterval: constants.NETWORK_API_RATE_LIMIT_MAX_NUMBER,
});
this._initializeRateLimiters();

Libp2p.create(initializationObject).then((node) => {
this.node = node;
Expand All @@ -94,6 +91,25 @@ class Libp2pService {
});
}

_initializeRateLimiters() {
const basicRateLimiter = new InMemoryRateLimiter({
interval: constants.NETWORK_API_RATE_LIMIT.TIME_WINDOW_MILLS,
maxInInterval: constants.NETWORK_API_RATE_LIMIT.MAX_NUMBER,
});

const spamDetection = new InMemoryRateLimiter({
interval: constants.NETWORK_API_SPAM_DETECTION.TIME_WINDOW_MILLS,
maxInInterval: constants.NETWORK_API_SPAM_DETECTION.MAX_NUMBER,
});

this.rateLimiter = {
basicRateLimiter,
spamDetection,
}

this.blackList = {};
}

_initializeNodeListeners() {
this.node.on('peer:discovery', (peer) => {
this._onPeerDiscovery(peer);
Expand Down Expand Up @@ -167,14 +183,13 @@ class Libp2pService {
this.node.handle(eventName, async (handlerProps) => {
const {stream} = handlerProps;
let timestamp = Date.now();
const blocked = await this.limiter.limit(handlerProps.connection.remotePeer.toB58String());
if(blocked) {
const remotePeerId = handlerProps.connection.remotePeer._idB58String;
if(await this.limitRequest(remotePeerId)) {
const preparedBlockedResponse = await this.prepareForSending(constants.NETWORK_RESPONSES.BLOCKED);
await pipe(
[preparedBlockedResponse],
stream
);
this.logger.info(`Blocking request from ${handlerProps.connection.remotePeer._idB58String}. Max number of requests exceeded.`);
return;
}
let data = await pipe(
Expand All @@ -190,10 +205,10 @@ class Libp2pService {
)
try {
data = await this.workerPool.exec('JSONParse', [data.toString()]);
this.logger.info(`Receiving message from ${handlerProps.connection.remotePeer._idB58String} to ${this.config.id}: event=${eventName};`);
this.logger.info(`Receiving message from ${remotePeerId} to ${this.config.id}: event=${eventName};`);
if (!async) {
const result = await handler(data);
this.logger.info(`Sending response from ${this.config.id} to ${handlerProps.connection.remotePeer._idB58String}: event=${eventName};`);
this.logger.info(`Sending response from ${this.config.id} to ${remotePeerId}: event=${eventName};`);
const preparedData = await this.prepareForSending(result);
await pipe(
[Buffer.from(preparedData)],
Expand All @@ -206,12 +221,12 @@ class Libp2pService {
stream
)

this.logger.info(`Sending response from ${this.config.id} to ${handlerProps.connection.remotePeer._idB58String}: event=${eventName};`);
this.logger.info(`Sending response from ${this.config.id} to ${remotePeerId}: event=${eventName};`);
const result = await handler(data);
if (Date.now() <= timestamp + timeout) {
await this.sendMessage(`${eventName}/result`, result, handlerProps.connection.remotePeer);
} else {
this.logger.warn(`Too late to send response from ${this.config.id} to ${handlerProps.connection.remotePeer._idB58String}: event=${eventName};`);
this.logger.warn(`Too late to send response from ${this.config.id} to ${remotePeerId}: event=${eventName};`);
}
}
} catch (e) {
Expand Down Expand Up @@ -267,6 +282,38 @@ class Libp2pService {
return false;
}

async limitRequest(remotePeerId) {
if(this.blackList[remotePeerId]){
const remainingMinutes = Math.floor(
constants.NETWORK_API_BLACK_LIST_TIME_WINDOW_MINUTES -
(Date.now() - this.blackList[remotePeerId]) / (1000 * 60)
);

if(remainingMinutes > 0) {
this.logger.info(`Blocking request from ${remotePeerId}. Node is blacklisted for ${remainingMinutes} minutes.`);

return true;
} else {
delete this.blackList[remotePeerId]
}
}

if(await this.rateLimiter.spamDetection.limit(remotePeerId)) {
this.blackList[remotePeerId] = Date.now();
this.logger.info(
`Blocking request from ${remotePeerId}. Spammer detected and blacklisted for ${constants.NETWORK_API_BLACK_LIST_TIME_WINDOW_MINUTES} minutes.`
);

return true;
} else if (await this.rateLimiter.basicRateLimiter.limit(remotePeerId)) {
this.logger.info(`Blocking request from ${remotePeerId}. Max number of requests exceeded.`);

return true;
}

return false;
}

async restartService() {
this.logger.info('Restrating libp2p service...');
// TODO: reinitialize service
Expand Down
55 changes: 28 additions & 27 deletions modules/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,46 +12,47 @@ exports.DID = 'DID';
exports.MAX_FILE_SIZE = 2621440;

/**
* @constant {number} SERVICE_API_RATE_LIMIT_TIME_WINDOW_MILLS
* - Express rate limit time window in milliseconds
* @constant {object} SERVICE_API_RATE_LIMIT
* - Express rate limit configuration constants
*/
exports.SERVICE_API_RATE_LIMIT_TIME_WINDOW_MILLS = 1 * 60 * 1000;

/**
* @constant {number} SERVICE_API_RATE_LIMIT_MAX_NUMBER
* - Express rate limit max number of requests allowed in the specified time window
*/
exports.SERVICE_API_RATE_LIMIT_MAX_NUMBER = 10;

/**
* @constant {number} SERVICE_API_SLOW_DOWN_TIME_WINDOW_MILLS
* - Express slow down time window in milliseconds
*/
exports.SERVICE_API_SLOW_DOWN_TIME_WINDOW_MILLS = 1 * 60 * 1000;
exports.SERVICE_API_RATE_LIMIT = {
TIME_WINDOW_MILLS: 1 * 60 * 1000,
MAX_NUMBER: 10,
};

/**
* @constant {number} SERVICE_API_SLOW_DOWN_DELAY_AFTER
* - Express slow down number of seconds after which it starts delaying requests
* @constant {object} SERVICE_API_SLOW_DOWN
* - Express slow down configuration constants
*/
exports.SERVICE_API_SLOW_DOWN_DELAY_AFTER = 5;
exports.SERVICE_API_SLOW_DOWN = {
TIME_WINDOW_MILLS: 1 * 60 * 1000,
DELAY_AFTER_SECONDS: 5,
DELAY_MILLS: 3 * 1000,
};

/**
* @constant {number} SERVICE_API_SLOW_DOWN_DELAY_MILLS
* - Express slow down delay between requests in milliseconds
* @constant {object} NETWORK_API_RATE_LIMIT
* - Network (Libp2p) rate limiter configuration constants
*/
exports.SERVICE_API_SLOW_DOWN_DELAY_MILLS = 3 * 1000;
exports.NETWORK_API_RATE_LIMIT = {
TIME_WINDOW_MILLS: 1 * 60 * 1000,
MAX_NUMBER: this.SERVICE_API_RATE_LIMIT.MAX_NUMBER,
};

/**
* @constant {number} NETWORK_API_RATE_LIMIT_TIME_WINDOW_MILLS
* - Network (Libp2p) rate limit time window in milliseconds
* @constant {object} NETWORK_API_SPAM_DETECTION
* - Network (Libp2p) spam detection rate limiter configuration constants
*/
exports.NETWORK_API_RATE_LIMIT_TIME_WINDOW_MILLS = 1 * 60 * 1000;
exports.NETWORK_API_SPAM_DETECTION = {
TIME_WINDOW_MILLS: 1 * 60 * 1000,
MAX_NUMBER: 20,
};

/**
* @constant {number} NETWORK_API_RATE_LIMIT_MAX_NUMBER
* - Network (Libp2p) rate limit max number of requests allowed in the specified time window
* @constant {object} NETWORK_API_BLACK_LIST_TIME_WINDOW_MINUTES
* - Network (Libp2p) black list time window in minutes
*/
exports.NETWORK_API_RATE_LIMIT_MAX_NUMBER = 10;
exports.NETWORK_API_BLACK_LIST_TIME_WINDOW_MINUTES = 60;

/**
* @constant {number} DID_PREFIX
Expand Down
16 changes: 8 additions & 8 deletions modules/controller/rpc-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,19 +126,19 @@ class RpcController {

initializeRateLimitMiddleware() {
this.rateLimitMiddleware = rateLimit({
windowMs: constants.SERVICE_API_RATE_LIMIT_TIME_WINDOW_MILLS,
max: constants.SERVICE_API_RATE_LIMIT_MAX_NUMBER,
message: `Too many requests sent, maximum number of requests per minute is ${constants.SERVICE_API_RATE_LIMIT_MAX_NUMBER}`,
windowMs: constants.SERVICE_API_RATE_LIMIT.TIME_WINDOW_MILLS,
max: constants.SERVICE_API_RATE_LIMIT.MAX_NUMBER,
message: `Too many requests sent, maximum number of requests per minute is ${constants.SERVICE_API_RATE_LIMIT.MAX_NUMBER}`,
standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
legacyHeaders: false, // Disable the `X-RateLimit-*` headers
});
}

initializeSlowDownMiddleWare() {
this.slowDownMiddleware = slowDown({
windowMs: constants.SERVICE_API_SLOW_DOWN_TIME_WINDOW_MILLS,
delayAfter: constants.SERVICE_API_SLOW_DOWN_DELAY_AFTER,
delayMs: constants.SERVICE_API_SLOW_DOWN_DELAY_MILLS,
windowMs: constants.SERVICE_API_SLOW_DOWN.TIME_WINDOW_MILLS,
delayAfter: constants.SERVICE_API_SLOW_DOWN.DELAY_AFTER_SECONDS,
delayMs: constants.SERVICE_API_SLOW_DOWN.DELAY_MILLS,
});
}

Expand Down Expand Up @@ -191,11 +191,11 @@ class RpcController {
this.logger.info(`Service API module enabled, server running on port ${this.config.rpcPort}`);

this.app.post('/publish', this.rateLimitMiddleware, this.slowDownMiddleware, async (req, res, next) => {
await this.publish(req, res, next, {isAsset: false});
await this.publish(req, res, next, { isAsset: false });
});

this.app.post('/provision', this.rateLimitMiddleware, this.slowDownMiddleware, async (req, res, next) => {
await this.publish(req, res, next, {isAsset: true, ual: null});
await this.publish(req, res, next, { isAsset: true, ual: null });
});

this.app.post('/update', this.rateLimitMiddleware, this.slowDownMiddleware, async (req, res, next) => {
Expand Down

0 comments on commit 7a584a6

Please sign in to comment.