Ограничить количество запросов от одного 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', 194, 67, 18, 0),       sprintf('%c%c%c%c', 255, 255, 255, 0)  ),  'Gigabot'  => array(      sprintf('%c%c%c%c', 66, 231, 188, 0),       sprintf('%c%c%c%c', 255, 255, 255, 0)  ),  'Google1'  => array(      sprintf('%c%c%c%c', 209, 85, 128, 0),       sprintf('%c%c%c%c', 255, 255, 128, 0)  ),  'Google2'  => array(      sprintf('%c%c%c%c', 72, 14, 192, 0),       sprintf('%c%c%c%c', 255, 255, 192, 0)  ),  'Google3'  => array(      sprintf('%c%c%c%c', 66, 249, 64, 0),       sprintf('%c%c%c%c', 255, 255, 224, 0)  ),  'Google4'  => array(      sprintf('%c%c%c%c', 64, 68, 80, 0),       sprintf('%c%c%c%c', 255, 255, 248, 0)  ),  'Google5'  => array(      sprintf('%c%c%c%c', 66, 102, 0, 0),       sprintf('%c%c%c%c', 255, 255, 240, 0)  ),  'Google6'  => array(      sprintf('%c%c%c%c', 64, 233, 160, 0),       sprintf('%c%c%c%c', 255, 255, 224, 0)  ),  'Google7'  => array(      sprintf('%c%c%c%c', 216, 239, 32, 0),       sprintf('%c%c%c%c', 255, 255, 224, 0)  ),  'Mail.Ru1'  => array(      sprintf('%c%c%c%c', 195, 239, 211, 0),       sprintf('%c%c%c%c', 255, 255, 255, 0)  ),  'Mail.Ru2'  => array(      sprintf('%c%c%c%c', 94, 100, 181, 128),       sprintf('%c%c%c%c', 255, 255, 255, 128)  ),  'msnbot1'  => array(      sprintf('%c%c%c%c', 65, 52, 0, 0),       sprintf('%c%c%c%c', 255, 252, 0, 0)  ),  'msnbot2'  => array(      sprintf('%c%c%c%c', 207, 46, 0, 0),       sprintf('%c%c%c%c', 255, 255, 0, 0)  ),  'Rambler'  => array(      sprintf('%c%c%c%c', 81, 19, 64, 0),       sprintf('%c%c%c%c', 255, 255, 224, 0)  ),  'Yahoo1'  => array(      sprintf('%c%c%c%c', 74, 6, 0, 0),       sprintf('%c%c%c%c', 255, 255, 0, 0)  ),  'Yahoo2'  => array(      sprintf('%c%c%c%c', 69, 147, 64, 0),       sprintf('%c%c%c%c', 255, 255, 192, 0)  ),  'Yahoo3'  => array(      sprintf('%c%c%c%c', 72, 30, 0, 0),       sprintf('%c%c%c%c', 255, 255, 0, 0)  ),  'Yahoo4'  => array(      sprintf('%c%c%c%c', 67, 195, 0, 0),       sprintf('%c%c%c%c', 255, 255, 0, 0)  ),  'Yandex1'  => array(      sprintf('%c%c%c%c', 213, 180, 192, 0),       sprintf('%c%c%c%c', 255, 255, 224, 0)  ),  'Yandex2'  => array(      sprintf('%c%c%c%c', 77, 88, 0, 0),       sprintf('%c%c%c%c', 255, 255, 192, 0)  ),  'Yandex3'  => array(      sprintf('%c%c%c%c', 93, 158, 128, 0),       sprintf('%c%c%c%c', 255, 255, 192, 0)  ),  'Yandex4'  => array(      sprintf('%c%c%c%c', 95, 108, 128, 0),       sprintf('%c%c%c%c', 255, 255, 128, 0)  ),  'Yandex5'  => array(      sprintf('%c%c%c%c', 87, 250, 224, 0),       sprintf('%c%c%c%c', 255, 255, 224, 0)  ),  'LiveInternet' => array(      sprintf('%c%c%c%c', 88, 212, 202, 0),    sprintf('%c%c%c%c', 255, 255, 255, 192)  ),);/**----------------------------------------------------------* Функция создаёт в указанной директории файл, начинающийся* с буквы 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 прикрутить это имеет смысл и как это сделать?
Какие мысли у авторитетов по данной теме?