Сегодня поговорим о защите сервера от большого количества запросов.

Не секрет, что любая вычислительная система имеет ограничение на нагрузку. Зная это, несложно будет вывести её из строя, просто загрузив массой запросов. После этого она может стать неработоспособной, что и является положительным результатом для недоброжелателей, которые всё это затеяли.

DOS-атака — популярный тип атаки на систему. Заключается в том, что хакер посылает большое количество запросов серверу. Атака может производиться как с одного, так и с тысячи компьютеров. Почему бы и немного не защититься от подобных атак. Ниже представлена реализация контроля запросов к серверу. Для написания использовался javascript для платформы Node.js.

Создадим json-файл конфигураций:
{
  "enable": true,
  "maxReq":  20,
  "time": 10000
}

где enable-включатель защиты, maxReq — это максимальное количество запросов в time миллисекунд.

Класс Limiter будет отвечать за контроль запросов.
function Limiter() {
    this.requests = []; // Очередь запросов [значение = время запроса]
}

где переменная requests хранит очередь запросов. Значение элемента очереди — время запроса. Размер очереди не будет превышать maxReq.

Функция getRequestsCount() возвращает количество запросов к серверу за последние time миллисекунд.
Limiter.prototype.getRequestsCount = function() {
    var currentTime = new Date().getTime(); // текущее время
    var counter = 0; // счетчик запросов
    for(var i = 0; i<this.requests.length; ++i) {
        // Инкрементируем счетчик, если время запроса попадает в нужный интервал
        if(currentTime - this.requests[i] < params['time']) ++counter;
    }
    return counter;
}

Во время любого запроса к нашему сайту, будет вызываться функция newRequest(). Реализация ниже:
Limiter.prototype.newRequest = function() {
    // Выходим, если контроль трафика выключен
    if(!params['enable']) return undefined;

    // Если текущий запрос уже лишний, то кидаем исключение
    if(this.getRequestsCount() >= params['maxReq']) throw Error('Server is too busy');

    // Если очередь >= params['maxReq'], то удаляем старый запрос
    if(this.requests.length >= params['maxReq']) this.requests.shift();

    // И добавляем  в очередь новый запрос
    this.requests.push(new Date().getTime());

    // Вернем текущее количество запросов
    return this.getRequestsCount();
}

Применение контроля запросов можно произвести в методе обращения к серверу.
server.on('request', function(req, res) {
    // Запросы favicon откинем в сторону
    if(req.url === '/favicon.ico') return;

    var message = ''; // ответ клиенту
    try {
        // Добавим новый запрос в очередь
        var count = limiter.newRequest();

        // Если newRequest() не вернет исключение, то
        // выполняем необходимые вычисления и генерируем
        // страницу для пользователя
        message = 'HARD SCRIPT! WOW WOW!!!\n';
        message += 'Requests for last '
            + limiter.getTime() + ' ms: '
            + count;
    } catch(e) {
        // Если newRequest() кидает исключение, то
        // говорим пользователю, что сервер слишком
        // нагружен и не может выполнить вычисления
        message = e.message;
    }
    // Отправляем ответ пользователю
    res.end(message);
});

Весь код можно посмотреть на bitbucket bitbucket.org/denzo1993/limiter

Описанное решение от атак не является идеальным, но, возможно, может найти свое применение в ваших проектах.

Всем спасибо!

Комментарии (5)


  1. walkman7
    20.07.2015 14:06
    +8

    Сомнительная защита от DDoS, так как атакующий просто забьет очередь запросов и пользовательский (хороший) в ответочку получит throw Error('Server is too busy');


    1. crmMaster
      20.07.2015 15:18
      +4

      Именно. Данный код не избавит от Ddos, а ускорит отказ в обслуживании. Кроме того, поскольку нагрузка не будет выше определенного уровня, системы мониторинга просто не среагируют на проблему.

      Так что это не решение, а костыль, да еще мешающий понять истинную проблему.


      1. denzo1993 Автор
        20.07.2015 18:31
        -1

        Все-таки это решение подойдёт больше для контроля нагрузки сервера. Например, позволяющее производить только n операций в t времени.


    1. denzo1993 Автор
      20.07.2015 18:30
      -2

      Согласен, что в итоге система будет для «хорошего» клиента неработоспособна. В таком случае можно как-нибудь допилить класс и добавить фильтрацию ip-адресов.


      1. walkman7
        20.07.2015 18:33
        +2

        Вот и пришли мы к тому, что ваше решение никому не нужно кроме вас, так как с этой задачей справляется nginx.org/en/docs/http/ngx_http_limit_req_module.html