Ограничить количество запросов от одного IP юзера

Автор Beer, 19 июня 2010, 00:10:06

« назад - далее »

0 Пользователи и 1 гость просматривают эту тему.

Beer

Однажды на форуме IPB нажал дважды поиск, в ответ сразу получил мессагу типо: "Администратор включил флуд-контроль!", повторите через 15 сек. :(
Сделано явно для защиты сервера от лишней нагрузки, создаваемой бестолковыми пользователями, учитывая то, что для меня актуально, пошел искать, и вот что нашел:
Случилось мне однажды столкнуться с ситуацией, когда мой хостер предъявил мне претензию о том, что мой акаунт создаёт непомерно большую нагрузку на MySQL-сервер. Посмотрев логи, я заметил, что такую нагрузку создают программы-качалки, которые копируют сайт целиком на локальный компьютер. Во время обращения к странице происходит несколько sql-запросов к базе данных. А если учесть, что эти программы готовы скачивать сразу несколько страниц с сайта, то получается, что в секунду идёт от 3 до 10 запросов. При такой «атаке» серверу действительно приходится не сладко.
Решением я увидел ограничение доступа к сайту с одного ip-адреса чаще, чем один раз в 2 секунды. (Это значение можно регулировать). Проверка происходит без использования sql-сервера, поэтому идёт достаточно быстро.
<?php
/*
*--------------------------------------------------------
* Модуль antiddos.php V3.1.2 Вт 16 Март 2010
* Copyright (C) Андрей Якушев, 2006. http://avy.ru
*--------------------------------------------------------
* Модуль предназначен для ограничения доступа к сайту или 
* к страницам, где он включён.
* Принцип работы в том, что запоминается ip-адрес и время
* обращения с этого адреса. И если в течение заданного
* времени происходит обращение с того же адреса, то ему
* выдаётся ошибка 503.
* Если количество недопустимых обращений подряд превышает
* определённое число, ip-адрес закрывается через файл
* .htaccess
* Модуль необходимо подключать к скрипту самым первым.
* Этим обеспечивается быстрота его работы.
*--------------------------------------------------------
*/

/* Директория для временных файлов. Необходимо указать 
  отдельную директорию, т.к. большое количество файлов
  в одной папке змедляет скорость обращения к ней. */
define ('AD_DIRNAME'$_SERVER['DOCUMENT_ROOT'] . '/tmp_path');
/* Время задержки в секуднах, в течение которого нельзя
  обращаться к сайту. */
define ('AD_DELAY'2);
/* Количество запрещённых повторений, после которых ip-адрес 
  будет забанен. Нужно обратить внимание на то, что некоторые
  программы чтения RSS-каналов считывают все ссылки, помещённые
  в канале сразу. Поэтому, если на сайте есть такие каналы,
  это число необходимо поставить больше, чем максимальное
  количество элементов в канале. */
define ('AD_TRYING'35);

/*
*---------------------------------------------------------------
* Список поисковых роботов.
* Очень не хорошо, если поисковый робот будет натыкаться
* на ошибки на сайте. Ему это может сильно не понравиться.
* Поэтому пишем список ip-адресов роботов; добавляем или
* удаляем, что нужно.
* IP-адреса:
* Alta Vista - см. Yahoo 
* Aport - 194.67.18.
* Gigabot - 66.231.188. (66.231.188.0/24)
* Google - 209.85.128.0 - 209.85.255.255 (209.85.128.0/17), 
*    72.14.192.0 - 72.14.255.255 (72.14.192.0/18),  
*    66.249.64.0 - 66.249.95.255 (66.249.64.0/19), 
*    64.68.80.0 - 64.68.87.255 (64.68.80.0/21), 
*    66.102.0.0 - 66.102.15.255 (66.102.0.0/20), 
*    64.233.160.0 - 64.233.175.255 (64.233.160.0/19), 
*    216.239.32.0 - 216.239.63.255 (216.239.32.0/19)
* Mail.Ru - 195.239.211.0 (195.239.211.0/24), 
*    94.100.181.128 - 94.100.181.255 (94.100.181.128/25)
* msnbot - 65.52.0.0 - 65.55.255.255 (65.52.0.0/14),
*    207.46.0.0 - 207.46.255.255 (207.46.0.0/16)
* Rambler - 81.19.64.0 - 81.19.66.255 (81.19.64.0/19)
* Yahoo - 74.6.0.0 - 74.6.255.255 (74.6.0.0/16), 
*    69.147.64.0 - 69.147.127.255 (69.147.64.0/18)
*    72.30.64.0 - 72.30.255.255 (72.30.0.0/16)
*    67.195.0.0 - 67.195.255.255 (67.195.0.0/16)
* Yandex - 213.180.214.128 - 213.180.214.255 (213.180.192.0/19), 
*    77.88.22.0 - 77.88.23.255 (77.88.0.0/18)
*    93.158.128.0 - 93.158.191.255 (93.158.128.0/18)
*    95.108.128.0 - 95.108.255.255 (95.108.128.0/17)
*    87.250.224.0 - 87.250.255.255 (87.250.224.0/19)
* LiveInternet - 88.212.202.0 - 88.212.202.63 (88.212.202.0/26)
* Ip-адреса и маски к ним кодируются в виде строки из 4 символов
* Это сделано из-за того, что невозможно в PHP использовать
* стандартными (простыми) процедурами 32-битное целое число
* без знака. А использование специальных библиотек усложнит
* работу и сделает скрипт зависимым от этих библиотек.
*---------------------------------------------------------------
*/
$ad_Robots_IP = array(
  
'Aport'      => array(
      
sprintf('%c%c%c%c'19467180), 
      
sprintf('%c%c%c%c'2552552550)
  ),
  
'Gigabot'  => array(
      
sprintf('%c%c%c%c'662311880), 
      
sprintf('%c%c%c%c'2552552550)
  ),
  
'Google1'  => array(
      
sprintf('%c%c%c%c'209851280), 
      
sprintf('%c%c%c%c'2552551280)
  ),
  
'Google2'  => array(
      
sprintf('%c%c%c%c'72141920), 
      
sprintf('%c%c%c%c'2552551920)
  ),
  
'Google3'  => array(
      
sprintf('%c%c%c%c'66249640), 
      
sprintf('%c%c%c%c'2552552240)
  ),
  
'Google4'  => array(
      
sprintf('%c%c%c%c'6468800), 
      
sprintf('%c%c%c%c'2552552480)
  ),
  
'Google5'  => array(
      
sprintf('%c%c%c%c'6610200), 
      
sprintf('%c%c%c%c'2552552400)
  ),
  
'Google6'  => array(
      
sprintf('%c%c%c%c'642331600), 
      
sprintf('%c%c%c%c'2552552240)
  ),
  
'Google7'  => array(
      
sprintf('%c%c%c%c'216239320), 
      
sprintf('%c%c%c%c'2552552240)
  ),
  
'Mail.Ru1'  => array(
      
sprintf('%c%c%c%c'1952392110), 
      
sprintf('%c%c%c%c'2552552550)
  ),
  
'Mail.Ru2'  => array(
      
sprintf('%c%c%c%c'94100181128), 
      
sprintf('%c%c%c%c'255255255128)
  ),
  
'msnbot1'  => array(
      
sprintf('%c%c%c%c'655200), 
      
sprintf('%c%c%c%c'25525200)
  ),
  
'msnbot2'  => array(
      
sprintf('%c%c%c%c'2074600), 
      
sprintf('%c%c%c%c'25525500)
  ),
  
'Rambler'  => array(
      
sprintf('%c%c%c%c'8119640), 
      
sprintf('%c%c%c%c'2552552240)
  ),
  
'Yahoo1'  => array(
      
sprintf('%c%c%c%c'74600), 
      
sprintf('%c%c%c%c'25525500)
  ),
  
'Yahoo2'  => array(
      
sprintf('%c%c%c%c'69147640), 
      
sprintf('%c%c%c%c'2552551920)
  ),
  
'Yahoo3'  => array(
      
sprintf('%c%c%c%c'723000), 
      
sprintf('%c%c%c%c'25525500)
  ),
  
'Yahoo4'  => array(
      
sprintf('%c%c%c%c'6719500), 
      
sprintf('%c%c%c%c'25525500)
  ),
  
'Yandex1'  => array(
      
sprintf('%c%c%c%c'2131801920), 
      
sprintf('%c%c%c%c'2552552240)
  ),
  
'Yandex2'  => array(
      
sprintf('%c%c%c%c'778800), 
      
sprintf('%c%c%c%c'2552551920)
  ),
  
'Yandex3'  => array(
      
sprintf('%c%c%c%c'931581280), 
      
sprintf('%c%c%c%c'2552551920)
  ),
  
'Yandex4'  => array(
      
sprintf('%c%c%c%c'951081280), 
      
sprintf('%c%c%c%c'2552551280)
  ),
  
'Yandex5'  => array(
      
sprintf('%c%c%c%c'872502240), 
      
sprintf('%c%c%c%c'2552552240)
  ),
  
'LiveInternet' => array(
      
sprintf('%c%c%c%c'882122020),
    
sprintf('%c%c%c%c'255255255192)
  ),
);

/*
*----------------------------------------------------------
* Функция создаёт в указанной директории файл, начинающийся
* с буквы a (для отличия от других возможных файлов) и
* содержащий в имени ip-адрес клиента.
* Если количество обращений подряд с данного адреса
* превысило допустимый предел, адрес закрывается через файл
* .htaccess
* При желании может быть отослано письмо на специальный
* адрес, в котором будет передана информация о действиях
* с заблокированного адреса.
*----------------------------------------------------------
*/
function ad_WriteIP($counter)
{
  
$counter++;
  if (
$counter AD_TRYING)
  {
      
//Баним ip-адрес
      
$f fopen($_SERVER['DOCUMENT_ROOT'] . '/.htaccess''a');
      
fwrite($f"\ndeny from " $_SERVER['REMOTE_ADDR']);
      
fclose($f);
      
// Открыть комментарии, если нужно уведомлять по почте
      /*
      //Получаем хост (в некоторых логах он может быть вместо ip
      $host = gethostbyaddr($_SERVER['REMOTE_ADDR']);
      $mess = 'Заблокирован адрес ' . $_SERVER['REMOTE_ADDR'] . 
        ' (' . $host . ")\n\n";
      //Выполняем запрос к логам. Нужно указать путь и имя лог-файла
      exec('cat /home/your_dir/logs/access_log | egrep \'(' . 
        str_replace('.', '\\.', $_SERVER['REMOTE_ADDR']) . ')|(' . 
        str_replace('.', '\\.', $host) . ')\' | sort -k 4 >' . 
        AD_DIRNAME . '/dump.txt');
      $mess .= file_get_contents(AD_DIRNAME . '/dump.txt');
      @ unlink(AD_DIRNAME . '/dump.txt');
      $email = 'my_box@myhost.ru'; //Укажите свой e-mail
      mail($email, 'IP-address was banned', $mess, "From: " . $email . 
        "\nReply-To: " . $email . 
        "\nContent-Type: text/plain; charset=Windows-1251" .
        "\nContent-Transfer-Encoding: 8bit");
      */
  
}
  else
  {
      
//Записываем файл с ip-адресом и количеством обращений
      
$f fopen(AD_DIRNAME '/a' $_SERVER['REMOTE_ADDR'] . '_' 
        
$counter'w');
      
fclose($f);
  }
}
/*
*----------------------------------------------------
* Проверка на отношение ip-адреса к сетям поисковиков
*----------------------------------------------------
*/
$ad_IsRobot false;
$ad_IP explode('.'$_SERVER['REMOTE_ADDR']);
$ad_IPMatch sprintf('%c%c%c%c'$ad_IP[0], $ad_IP[1], $ad_IP[2], $ad_IP[3]);
foreach (
$ad_Robots_IP as $ad_match)
{
  
//Если на входящий адрес наложить маску операцией "и",
  //то он должен будет совпасть с начальным адресом сети.
  
if (($ad_IPMatch $ad_match[1]) == $ad_match[0])
  {
      
$ad_IsRobot true;
      break;
  }
}

/*
*---------------------------------------------------------
* Поисковые роботы не любят, когда к адресу страницы
* добавляется переменная сессии. Поэтому, если на сайте
* используются сессии, то их лучше включать, если агент -
* не робот.
* Если сессии не используются, то этот кусок можно убрать.
*---------------------------------------------------------
*/
/*
if (!$ad_IsRobot)
{
  session_start();
}
else
{
  /* Чтобы поисковый робот не сильно загружал сайт,
  делаем ему задержку */
  
sleep(AD_DELAY);
}
*/

if (!
$ad_IsRobot)
{
  
/*** Чтение каталога и удаление старых файлов ***/
  
$ad_dir      opendir(AD_DIRNAME) or 
      die(
'Отсутствует директория для временных файлов');
  
$ad_forbid  time() - AD_DELAY;
  
/* IP-адрес в имени файла, начинающегося на букву a, 
  а время обращения - время изменения файла */
  
$ad_before_trying 0;
  while (
false !== ($ad_FName readdir($ad_dir)))
  {
      if (
ereg('^a[1-9]'$ad_FName))
      {
        if (@ 
filemtime(AD_DIRNAME '/' $ad_FName) < $ad_forbid){
            @ 
unlink(AD_DIRNAME '/' $ad_FName);
        }
        elseif (
ereg('^a' str_replace('.''\\.'
            
$_SERVER['REMOTE_ADDR']) . '_([0-9]+)$'$ad_FName$ad_match))
        {
            
//Если файл есть, то читаем, сколько раз к нму обращались
            
$ad_before_trying intval($ad_match[1]);
            @ 
unlink(AD_DIRNAME '/' $ad_FName);
        }
      }
  }
  
closedir($ad_dir);
  
/*** Выводить или не выводить сообщение об ошибке ***/
  
if ($ad_before_trying 0)
  {
      
/* Если обращение было недавно, то выводим сообщение об ошибке */
      
header ('HTTP/1.0 503 Service Unavailable');
      
header ('Status: 503 Service Unavailable');
      
header ('Retry-After: ' $ad_delay 3);
?>

<!doctype html public "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>Ошибка 503</title>
<meta http-equiv="Content-Type" content="text/html; charset=Windows-1251" />
</head>
<body>
<h1>Ошибка 503 (Service Unavailable)</h1>
<p>Сервер не может в данный момент выдать запрашиваемую Вами страницу.
Попробуйте вызвать эту страницу позже (клавиша F5).</p>
</body>
</html>
<?php
      ad_WriteIP
($ad_before_trying);  // Перед выходом записываем ip
      
exit;
  }else{
      
ad_WriteIP($ad_before_trying);
  }
}
?>


Источник: http://avy.ru/ftopic1870.html
Данное решение задействовал на Joomla прописав в шаблоне index.php - нагрузка упала на 10-15%. Значит работает.

И еще нашел: Модуль для защиты сайта/форума от флуда
http://php.spb.ru/other/_dima_noflood.php

Может и к SMF прикрутить это имеет смысл и как это сделать?
Какие мысли у авторитетов по данной теме?