【问题标题】:Node socket.io, anything to prevent flooding?节点socket.io,有什么可以防止泛滥的吗?
【发布时间】:2014-04-02 07:57:39
【问题描述】:

我怎样才能阻止某人简单地做

while(true){client.emit('i am spammer', true)};

当有人想要让我的节点服务器崩溃时,这肯定是个问题!

【问题讨论】:

  • 在收到洪水后终止连接(在短时间内一定数量的消息)是否可以接受?当然,他们可以在之后重新连接,但这会将问题转移到经典 DoS 保护领域。
  • WebSockets 毕竟只是套接字。通过防火墙的典型 DoS 保护可能就足够了。您还可以实现诸如限制套接字之类的东西,如果套接字保持在高流量状态,则超时就会被丢弃。套接字具有会话亲和性,因此它实际上使监视和限制套接字变得非常容易。

标签: node.js socket.io


【解决方案1】:

就像 tsrurzl 所说,您需要实现 rate limiter(限制套接字)。

以下代码示例仅在您的套接字返回缓冲区(而不是字符串)时才能可靠地工作。该代码示例假定您将首先调用 addRatingEntry(),然后立即调用 evalRating()。否则,在 evalRating() 根本没有被调用或太迟调用的情况下,您将面临内存泄漏的风险。

var rating, limit, interval;

rating = []; // rating: [*{'timestamp', 'size'}]
limit = 1048576; // limit: maximum number of bytes/characters.
interval = 1000; // interval: interval in milliseconds.
// Describes a rate limit of 1mb/s

function addRatingEntry (size) {
    // Returns entry object.
    return rating[(rating.push({
        'timestamp': Date.now(),
        'size': size
    }) - 1);
}

function evalRating () {
// Removes outdated entries, computes combined size, and compares with limit variable.
// Returns true if you're connection is NOT flooding, returns false if you need to disconnect.
    var i, newRating, totalSize;
    // totalSize in bytes in case of underlying Buffer value, in number of characters for strings. Actual byte size in case of strings might be variable => not reliable.
    newRating = [];
    for (i = rating.length - 1; i >= 0; i -= 1) {
        if ((Date.now() - rating[i].timestamp) < interval) {
            newRating.push(rating[i]);
        }
    }
    rating = newRating;

    totalSize = 0;
    for (i = newRating.length - 1; i >= 0; i -= 1) {
        totalSize += newRating[i].timestamp;
    }

    return (totalSize > limit ? false : true);
}

// Assume connection variable already exists and has a readable stream interface
connection.on('data', function (chunk) {
    addRatingEntry(chunk.length);
    if (evalRating()) {
         // Continue processing chunk.
    } else {
         // Disconnect due to flooding.
    }
});

您可以添加额外的检查,例如检查 size 参数是否真的是数字等。

附录:确保每个连接都包含(在闭包中)评级、限制和间隔变量,并且它们没有定义全局速率(每个连接操纵相同的评级) .

【讨论】:

    【解决方案2】:

    我实现了一个小洪水功能,并不完美(请参阅下面的改进),但当用户提出太多请求时,它会断开用户的连接。

    // Not more then 100 request in 10 seconds
    let FLOOD_TIME = 10000;
    let FLOOD_MAX = 100;
    
    let flood = {
        floods: {},
        lastFloodClear: new Date(),
        protect: (io, socket) => {
    
            // Reset flood protection
            if( Math.abs( new Date() - flood.lastFloodClear) > FLOOD_TIME ){
                flood.floods = {};
                flood.lastFloodClear = new Date();
            }
    
            flood.floods[socket.id] == undefined ? flood.floods[socket.id] = {} : flood.floods[socket.id];
            flood.floods[socket.id].count == undefined ? flood.floods[socket.id].count = 0 : flood.floods[socket.id].count;
            flood.floods[socket.id].count++;
    
            //Disconnect the socket if he went over FLOOD_MAX in FLOOD_TIME
            if( flood.floods[socket.id].count > FLOOD_MAX){
                console.log('FLOODPROTECTION ', socket.id)
                io.sockets.connected[socket.id].disconnect();
                return false;
            }
    
            return true;
        }
    }
    
    exports = module.exports = flood;
    

    然后像这样使用它:

    let flood = require('../modules/flood')
    
    // ... init socket io...
    
    socket.on('message', function () {
        if(flood.protect(io, socket)){
            //do stuff
        }   
    });
    

    改进将是,在计数旁边添加另一个值,即他断开连接的频率,然后创建一个禁止列表,不再让他连接。此外,当用户刷新页面时,他会获得一个新的 socket.id,因此可能在这里使用唯一的 cookie 值而不是 socket.id

    【讨论】:

      【解决方案3】:

      这里是简单的rate-limiter-flexible 包示例。

      const app = require('http').createServer();
      const io = require('socket.io')(app);
      const { RateLimiterMemory } = require('rate-limiter-flexible');
      
      app.listen(3000);
      
      const rateLimiter = new RateLimiterMemory(
        {
          points: 5, // 5 points
          duration: 1, // per second
        });
      
      io.on('connection', (socket) => {
        socket.on('bcast', async (data) => {
          try {
            await rateLimiter.consume(socket.handshake.address); // consume 1 point per event from IP
            socket.emit('news', { 'data': data });
            socket.broadcast.emit('news', { 'data': data });
          } catch(rejRes) {
            // no available points to consume
            // emit error or warning message
            socket.emit('blocked', { 'retry-ms': rejRes.msBeforeNext });
          }
        });
      });
      

      阅读更多official docs

      【讨论】:

        猜你喜欢
        • 2023-03-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-05-13
        • 2016-10-13
        • 1970-01-01
        • 1970-01-01
        • 2013-08-11
        相关资源
        最近更新 更多