HTTP_FloodControl Class

Introduction

The HTTP_FloodControl package can be used to detect and protect a Web site from attempts to flood it with too many requests. It also allows to protect the site from automatic downloading many pages or files from the same IP address, session ID or other unique identifier.

The detection of flood is determine according to a set of parameters indicating the maximal allowed number of requests for the certain time interval. It is possible to set several parameters at once in order to perform more effective protection.

The package uses various storage containers (regular files, DB, MDB, MDB2) to handle counter logs.

License Agreement

This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.

Credits

Written by Vagharshak Tozalakyan © 2007
This package is based on FloodBlocker class previously created by author for PHP Classes project.

Requirements and Dependences

  • PHP version 5
  • PEAR::Exception
  • PEAR::DB (optional)
  • PEAR::MDB (optional)
  • PEAR::MDB2 (optional)

See downloads section for direct links to dependent PEAR packages.

How To Use

Using HTTP_FloodControl is quite simple. Normally it is necessary to set an appropriate storage container, define limits and call the check() method, which returns false if flooding attempt detected based on supplied limits and unique identifier.

<?php
require_once 'HTTP/FloodControl.php';
try {
    
$ip HTTP_FloodControl::getUserIP();
} catch (
HTTP_FloodControl_Exception $e) {
    die(
$e);
}
try {
    
$fc =& new HTTP_FloodControl();
    
$fc->setContainer('File''/home/user1/fc_logs');
    
$limits = array (
        
10   => 10,  // maximum 10 requests in 10 seconds
        
60   => 30,  // maximum 30 requests in 60 seconds
        
300  => 50,  // maximum 50 requests in 300 seconds
        
3600 => 200  // maximum 200 requests in 3600 seconds
    
);
    if (!
$fc->check($limits$ip)) {
        die(
'Too many requests. Please try later.');
    }
} catch (
HTTP_FloodControl_Exception $e) {
    die(
$e);
}
echo 
'Hello world!';
?>

To run the above example it is necessary to have a directory /home/user1/fc_logs with writing permissions for the script.

Normally flood protection code should be written at the top of the script. The code may exist in a separate PHP file which is possible to include on desired pages of a site.

Using File Container

In order to use the File container it is necessary to create a separate directory for counter logs storage. The script must have permission to write files in that directory.

Logs directory may be created somewhere outside the root of documents or an .htaccess file may be used inside that directory to prevent illegal access. Please note, that the garbage collector will delete all files from the logs directory except those started with a dot.

<?php
require_once 'HTTP/FloodControl.php';
try {
    
$ip HTTP_FloodControl::getUserIP();
} catch (
HTTP_FloodControl_Exception $e) {
    die(
$e);
}
try {
    
$fc =& new HTTP_FloodControl();
    
$fc->setContainer('File''/home/user1/fc_logs');
    
// Probability of garbage collection, optional
    
$fc->setProbability(75);
    
// Maximum lifetime of counter logs, optional
    
$fc->setLifetime(7200);
    
$limits = array (
        
3600 => 100  // maximum 100 requests in 3600 seconds
    
);
    if (!
$fc->check($limits$ip)) {
        die(
'Too many requests. Please try later.');
    }
} catch (
HTTP_FloodControl_Exception $e) {
    die(
$e);
}
echo 
'Hello world!';
?>

Using Database Containers

First of all it is necessary to create a database table with the following structure:

CREATE TABLE fc_logs (
    unique_id varchar(32) NOT NULL,
    data text NOT NULL,
    access int UNSIGNED NOT NULL,
    PRIMARY KEY (unique_id)
)

In the following example MDB2 container is being used. DB and MDB containers may be used the same way, only MDB2 should be changed to the name of appropriate container.

<?php
require_once 'HTTP/FloodControl.php';
try {
    
$ip HTTP_FloodControl::getUserIP();
} catch (
HTTP_FloodControl_Exception $e) {
    die(
$e);
}
try {
    
$fc =& new HTTP_FloodControl();
    
$fc->setContainer('MDB2', array(
        
'dsn' => 'mysql://username:password@localhost/dbname',
        
'table' => 'fc_logs',
        
'autooptimize' => true
    
));
    
$limits = array (
        
300  => 10  // maximum 10 requests in 300 seconds
    
);
    if (!
$fc->check($limits$ip)) {
        die(
'Too many requests. Please try later.');
    }
} catch (
HTTP_FloodControl_Exception $e) {
    die(
$e);
}
echo 
'Hello world!';
?>

To use an existed database connection, a valid connection handle may be passed to the setContainer() instead of DSN string.

<?php
require_once 'MDB2.php';
require_once 
'HTTP/FloodControl.php';
$dsn 'mysql://username:password@localhost/dbname';
$dbh =& MDB2::connect($dsn);
if (
PEAR::isError($dbh)) {
    die(
getMessage($dbh));
}
try {
    
$ip HTTP_FloodControl::getUserIP();
} catch (
HTTP_FloodControl_Exception $e) {
    die(
$e);
}
try {
    
$fc =& new HTTP_FloodControl();
    
$fc->setContainer('MDB2', array(
        
'dsn' => $dbh,
        
'table' => 'fc_logs',
        
'autooptimize' => true
    
));
    
$limits = array (
        
300  => 10  // maximum 10 requests in 300 seconds
    
);
    if (!
$fc->check($limits$ip)) {
        die(
'Too many requests. Please try later.');
    }
} catch (
HTTP_FloodControl_Exception $e) {
    die(
$e);
}
echo 
'Hello world!';
?>

Applying Multiple Limits

The check() method may be called more than one time. For example, in the following listing two different limit arrays are used for subnet address and current session identifier.

<?php
session_start
();
require_once 
'HTTP/FloodControl.php';
try {
    
$fc =& new HTTP_FloodControl();
    
$result $fc->setContainer('MDB2', array(
        
'dsn' => 'mysql://root@localhost/test',
        
'table' => 'flood_logs',
        
'autooptimize' => true
    
));
    
// Optional probability of garbage collection
    
$fc->setProbability(50);
    
$ip_parts explode('.'$_SERVER['REMOTE_ADDR']);
    
$subnet_addr $ip_parts[0] . $ip_parts[1];
    if (!
$fc->check(array(300 => 103600 => 100), $subnet_addr)) {
        die(
'Too many requests from the same subnet.');
    } elseif (!
$fc->check(array(600 => 15), session_id())) {
        die(
'Too many requests with the same session ID.');
    }
} catch (
HTTP_FloodControl_Exception $e) {
    die(
$e);
}
echo 
'Hello world!';
?>

Incremental Locking

Let's assume the following code is used for flood control:

<?php
...
$fc->setIncrementalLock(true);
if (!
$fc->check(array(60 => 30))) {
    die(
'Flood detected!');
}
...
?>

If there was more than 30 requests during 1 minute, the access will be blocked for the next 1 minute, so the user can access the page only after 1 minute from the latest over-request. If the user try to access the locked page, the access will be blocked for 1 minute again (incrementally).

Incremental locking is set to false by default.

Downloads

HTTP_FloodControl-0.1.1.tgz

Here are some external packages you may download:

PEAR: PEAR base classes
DB: database abstraction layer
MDB: database abstraction layer
MDB2: database abstraction layer
MDB2_Driver_fbsql: fbsql MDB2 driver
MDB2_Driver_ibase: ibase MDB2 driver
MDB2_Driver_mssql: mssql MDB2 driver
MDB2_Driver_mysql: mysql MDB2 driver
MDB2_Driver_mysqli: mysqli MDB2 driver
MDB2_Driver_oci8: oci8 MDB2 driver
MDB2_Driver_pgsql: pgsql MDB2 driver
MDB2_Driver_querysim: querysim MDB2 driver
MDB2_Driver_sqlite: sqlite MDB2 driver