如何限制Node.JS中每个ip的请求数量?

时间:2014-10-10 19:37:12

标签: node.js

我试图想办法帮助减少我的node.js应用程序受到的损害,如果我得到DDOS攻击的话。我想限制每个IP的请求。我想将每个IP地址限制为每秒这么多请求。例如:没有IP地址每3秒钟可以超过10个请求。

到目前为止,我已经想出了这个:

 http.createServer(req, res, function() {
     if(req.connection.remoteAddress ?????? ) {
        block ip for 15 mins
      }
}

4 个答案:

答案 0 :(得分:4)

如果您想在应用服务器级别自行构建,则必须构建一个数据结构,记录来自特定IP地址的每个最近访问权限,以便在新请求到达时,您可以回顾历史记录和看看它是否做了太多的请求。如果是这样,则拒绝任何进一步的数据。并且,为了防止这些数据堆积在您的服务器中,您还需要某种清除代码来清除旧的访问数据。

以下是一种方法(未经测试的代码来说明这个想法):

function AccessLogger(n, t, blockTime) {
    this.qty = n;
    this.time = t;
    this.blockTime = blockTime;
    this.requests = {};
    // schedule cleanup on a regular interval (every 30 minutes)
    this.interval = setInterval(this.age.bind(this), 30 * 60 * 1000);
}

AccessLogger.prototype = {
    check: function(ip) {
        var info, accessTimes, now, limit, cnt;

        // add this access
        this.add(ip);

        // should always be an info here because we just added it
        info = this.requests[ip];
        accessTimes = info.accessTimes;

        // calc time limits
        now = Date.now();
        limit = now - this.time;

        // short circuit if already blocking this ip
        if (info.blockUntil >= now) {
            return false;
        }

        // short circuit an access that has not even had max qty accesses yet
        if (accessTimes.length < this.qty) {
            return true;
        }
        cnt = 0;
        for (var i = accessTimes.length - 1; i >= 0; i--) {
            if (accessTimes[i] > limit) {
                ++cnt;
            } else {
                // assumes cnts are in time order so no need to look any more
                break;
            }
        }
        if (cnt > this.qty) {
            // block from now until now + this.blockTime
            info.blockUntil = now + this.blockTime;
            return false;
        } else {
            return true;
        }

    },
    add: function(ip) {
        var info = this.requests[ip];
        if (!info) {
            info = {accessTimes: [], blockUntil: 0};
            this.requests[ip] = info;
        }
        // push this access time into the access array for this IP
        info.accessTimes.push[Date.now()];
    },
    age: function() {
        // clean up any accesses that have not been here within this.time and are not currently blocked
        var ip, info, accessTimes, now = Date.now(), limit = now - this.time, index;
        for (ip in this.requests) {
            if (this.requests.hasOwnProperty(ip)) {
                info = this.requests[ip];
                accessTimes = info.accessTimes;
                // if not currently blocking this one
                if (info.blockUntil < now) {
                    // if newest access is older than time limit, then nuke the whole item
                    if (!accessTimes.length || accessTimes[accessTimes.length - 1] < limit) {
                        delete this.requests[ip];
                    } else {
                        // in case an ip is regularly visiting so its recent access is never old
                        // we must age out older access times to keep them from 
                        // accumulating forever
                        if (accessTimes.length > (this.qty * 2) && accessTimes[0] < limit) {
                            index = 0;
                            for (var i = 1; i < accessTimes.length; i++) {
                                if (accessTimes[i] < limit) {
                                    index = i;
                                } else {
                                    break;
                                }
                            }
                            // remove index + 1 old access times from the front of the array
                            accessTimes.splice(0, index + 1);
                        }
                    }
                }
            }
        }
    }
};

var accesses = new AccessLogger(10, 3000, 15000);

// put this as one of the first middleware so it acts 
// before other middleware spends time processing the request
app.use(function(req, res, next) {
    if (!accesses.check(req.connection.remoteAddress)) {
        // cancel the request here
        res.end("No data for you!");
    } else {
        next();
    }
});

此方法在IP地址监控方面也有通常的限制。如果多个用户共享NAT后面的IP地址,则会将它们全部视为一个用户,并且由于其组合活动而可能会被阻止,而不是因为单个用户的活动。


但是,正如其他人所说,当请求到达你的服务器时,已经完成了一些DOS损坏(它已经从你的服务器中取出了周期)。在执行更昂贵的操作(如数据库操作)之前切断请求可能会有所帮助,但在更高级别(例如Nginx或防火墙或负载均衡器)检测和阻止此操作会更好。

答案 1 :(得分:2)

我认为这不应该在http服务器级别完成。基本上,它不会阻止用户访问您的服务器,即使他们在15分钟内看不到任何内容。

在我看来,你应该使用防火墙在你的系统中处理它。虽然这更像是ServerFaultSuperUser的讨论,但请允许我给您一些指示。

  1. 使用 iptables 在您的入口点设置防火墙(您的服务器或您有权访问该线路的其他任何内容)。 iptables允许您设置每个IP的最大连接数限制。如果您没有网络背景,那么学习曲线非常陡峭。这是传统的方式。

    这是一个面向初学者的好资源:Iptables for beginners

    类似于您需要的内容:Unix StackExchange

  2. 我最近遇到了一个名为Uncomplicated Firewall (ufw)的非常好的软件包,它碰巧有一个限制每个IP连接速率的选项,并在几分钟内完成设置。对于更复杂的东西,你仍然需要iptables。

    总之,像Brad所说,

      

    让您的应用程序服务器执行他们最擅长的操作...运行您的应用程序。

    让防火墙做他们最擅长的事情,从服务器中取出不需要的IP。

答案 2 :(得分:1)

如果您使用Nodejs过滤连接或应用连接策略,那就不好了。

最好在NodeJS前面使用Nginx

客户 - &gt; Nginx - &gt; Nodejs或Application。

这并不困难和便宜,因为Ngnix太开源了。

祝你好运。

答案 3 :(得分:1)

我们可以使用npm Package

npm i limiting-middleware

代码:

const LimitingMiddleware = require('limiting-middleware');
app.use(new LimitingMiddleware({ limit: 100, resetInterval: 1200000 }).limitByIp());
// 100 request limit. 1200000ms reset interval (20m).

有关更多信息:Click here