2013-06-24 64 views
0

我發現這個腳本PHP防洪改進腳本

Quick and easy flood protection?

,我已經把它變成一個功能。

適用於大多數情況。不時看到錯誤:

[<a href='function.unlink'>function.unlink</a>]: No such file or directory 
符合

else if ($diff>3600) { unlink($path); } // If first request was more than 1 hour, new ip file

顯然,由於某種原因,一些IP的文件越來越刪除嗎?

我試圖找到邏輯錯誤,但我並不擅長這一點。也許有人可以幫忙。

功能:

function ht_request_limiter() { 
    if (!isset($_SERVER['REMOTE_ADDR'])) { return; } // Maybe its impossible, however we check it first 
    if (empty($_SERVER['REMOTE_ADDR'])) { return; } // Maybe its impossible, however we check it first 
    $path = '/home/czivbaby/valuemarket.gr/ip-sec/'; // I use a function to validate a path first and return if false... 
    $path = $path.$_SERVER['REMOTE_ADDR'].'.txt'; // Real file path (filename = <ip>.txt) 
    $now = time(); // Current timestamp 
    if (!file_exists($path)) { // If first request or new request after 1 hour/24 hour ban, new file with <timestamp>|<counter> 
     if ($handle = fopen($path, 'w+')) { 
      if (fwrite($handle, $now.'|0')) { chmod($path, 0700); } // Chmod to prevent access via web 
      fclose($handle); 
     } 
    } 
    else if (($content = file_get_contents($path)) !== false) { // Load existing file 
     $content = explode('|',$content); // Create paraset [0] -> timestamp [1] -> counter 
     $diff = (int)$now-(int)$content[0]; // Time difference in seconds from first request to now 
     if ($content[1] == 'ban') { // If [1] = ban we check if it was less than 24 hours and die if so 
      if ($diff>86400) { unlink($path); } // 24 hours in seconds.. if more delete ip file 
      else { 
       header("HTTP/1.1 503 Service Unavailable"); 
       exit("Your IP is banned for 24 hours, because of too many requests."); 
      } 
     } 
     else if ($diff>3600) { unlink($path); } // If first request was more than 1 hour, new ip file 
     else { 
      $current = ((int)$content[1])+1; // Counter + 1 
      if ($current>200) { // We check rpm (request per minute) after 200 request to get a good ~value 
       $rpm = ($current/($diff/60)); 
       if ($rpm>10) { // If there was more than 10 rpm -> ban (if you have a request all 5 secs. you will be banned after ~17 minutes) 
        if ($handle = fopen($path, 'w+')) { 
         fwrite($handle, $content[0].'|ban'); 
         fclose($handle); 
         // Maybe you like to log the ip once -> die after next request 
        } 
        return; 
       } 
      } 
      if ($handle = fopen($path, 'w+')) { // else write counter 
       fwrite($handle, $content[0].'|'.$current .''); 
       fclose($handle); 
      } 
     } 
    } 
} 

回答

0

你的服務器正在處理兩個(或更多)的請求來自同一客戶在同一時間,劇本似乎並沒有正確處理這種(完全正常)的情況。 Web瀏覽器並行地從服務器下載多個對象,以加快瀏覽速度。很可能,瀏覽器不時地並行執行兩個請求,最終並行執行兩個請求,這樣,該腳本的兩個副本幾乎同時在同一個unlink()調用中結束。一個成功刪除該文件,另一個給出錯誤消息。

即使你的服務器只有一個CPU,操作系統將是愉快的背景下被執行相同的PHP腳本在同一時間對同一客戶端IP地址的多個PHP進程之間切換,提供多任務處理。

腳本可能應該使用文件鎖定(http://php.net/manual/en/function.flock.php)來鎖定文件。或者乾脆忽略unlink()錯誤(通過在unlink前放置一個@),但其他併發性問題很可能會出現。

腳本應該:

  1. 打開閱讀文件,並使用$f = fopen($filename, 'r+');
  2. 鎖使用的文件句柄打開的文件寫入。如果其他進程已經有鎖,則調用flock($f, LOCK_EX)將會阻塞並等待。
  3. 閱讀文件內容。
  4. 決定做什麼(增加計數器,拒絕服務)。
  5. fseek($f, 0, SEEK_SET)到文件開頭ftruncate($f, 0)使其變空並在必要時重寫文件內容如有必要,取消鏈接()文件。
  6. 關閉文件句柄與FCLOSE($ F),這也釋放它的鎖,讓另一個進程繼續執行步驟3

模式是同樣爲所有的編程語言。

+0

錯誤出現隨機。如果這是一個請求問題,在5000次命中時它肯定會出現。還從我的電腦模擬洪水,它不會產生錯誤。 – user2517612

+0

如果您沒有太多流量(5000次點擊並不多,除非在短時間內發生),您可能會很少看到錯誤,具體取決於您的Web服務器設置。您可以通過生成並行查詢來重現它。同時運行一個小腳本的多個副本:'while:;做curl -s -o/dev/null http://example.com/script.php;做' – oh7lzb

+0

我會(嘗試)實現一些文件鎖定。我不能接受腳本的OP,他可以很容易地添加它。 – user2517612