9

I am getting the IP address of a user using $_SERVER['REMOTE_ADDR'] who tries to visit my site. I have different boxes with different IDS and different CIDR entries in my database for each box. I want to check if the user IP exists in any of the CIDR or not for a specific box. If it exists then he is allowed to enter otherwise not. let suppose the user IP is

127.0.0.1

suppose The CIDR entries for box 12 is

192.168.1.20/27
192.168.0.10/32

The CIDR entries are stored in a column in database

if he come with an IP that comes inside this range then he should be allowed to enter the site otherwise not. I want to check his ip against each CIDR entry.

Any ideas please?

Thanks

Faryal Khan
  • 881
  • 4
  • 19
  • 37
  • possible duplicate of [matching an IP to a CIDR mask in php5?](http://stackoverflow.com/questions/594112/matching-an-ip-to-a-cidr-mask-in-php5) – sberry Apr 20 '12 at 09:34
  • [What have you tried](http://mattgemmell.com/2008/12/08/what-have-you-tried/)? – ghoti Apr 20 '12 at 11:42
  • @ghoti Maybe not everybody knows where to start - I know I didn't. –  Mar 08 '15 at 06:53

2 Answers2

12

OK, try following this 5 simple steps...

1. Store your CIDRs into array (read 'em from database; guess you know how to get this)

$cidrs = array(
  '192.168.1.20/27', 
  '192.168.0.10/32'
  );

2. Get user's IP (remote address)

$user_ip = $_SERVER['REMOTE_ADDR'];

3. Add this function

function IPvsCIDR($user_ip, $cidr) {
  $parts = explode('/', $cidr);
  $ipc = explode('.', $parts[0]);
  foreach ($ipc as &$v)
    $v = str_pad(decbin($v), 8, '0', STR_PAD_LEFT);
  $ipc = substr(join('', $ipc), 0, $parts[1]);
  $ipu = explode('.', $user_ip);
  foreach ($ipu as &$v)
    $v = str_pad(decbin($v), 8, '0', STR_PAD_LEFT);
  $ipu = substr(join('', $ipu), 0, $parts[1]);
  return $ipu == $ipc;
  }

4. Compare user's IP address against $cidrs

$validaddr = false;
foreach ($cidrs as $addr)
  if (IPvsCIDR($user_ip, $addr)) {
    $validaddr = true;
    break;
    } 

5. Decide what to do with user

if ($validaddr) {
  echo "CORRECT IP ADDRESS";
  }
else {
  echo "INCORRECT IP ADDRESS";
  }

That's it!

how this function works. It converts CIDR address-part (x.x.x.x) into binary string and takes first N digits. Then it does same job with user's IP and checks do values match.

Example 2 (complete job from function)

function testUserIP($user_ip, $cidrs) {
  $ipu = explode('.', $user_ip);
  foreach ($ipu as &$v)
    $v = str_pad(decbin($v), 8, '0', STR_PAD_LEFT);
  $ipu = join('', $ipu);
  $res = false;
  foreach ($cidrs as $cidr) {
    $parts = explode('/', $cidr);
    $ipc = explode('.', $parts[0]);
    foreach ($ipc as &$v) $v = str_pad(decbin($v), 8, '0', STR_PAD_LEFT);
    $ipc = substr(join('', $ipc), 0, $parts[1]);
    $ipux = substr($ipu, 0, $parts[1]);
    $res = ($ipc === $ipux);
    if ($res) break;
    }
  return $res;
  }

Usage:

$user_ip = $_SERVER['REMOTE_ADDR'];
$cidrs = array('192.168.1.20/27', '192.168.0.10/32'); 
if (testUserIP($user_ip, $cidrs)) {
  // user ip is ok
  }
else {
  // access denied
  }
Wh1T3h4Ck5
  • 8,399
  • 9
  • 59
  • 79
  • I am getting CIDRs from database and I want to check all the CIDRs against an IP of user – Faryal Khan Apr 20 '12 at 09:57
  • update your question and put some example of your database (structure + data) – Wh1T3h4Ck5 Apr 20 '12 at 09:58
  • Thanks for the help Can you please tell me where your foreach loops end ? – Faryal Khan Apr 20 '12 at 11:24
  • i guess you're talking about first example `foreach... if...`. It ends on same place where `if` line ends. you dont need brackets { } for single line commands and that `foreach` has only `if` line after. So instead of `foreach() { if() {...} }` i used `foreach() if() {...}`. – Wh1T3h4Ck5 Apr 20 '12 at 11:40
  • What about the foreach loops in IPvsCIDR function they also dont need any brackets? – Faryal Khan Apr 20 '12 at 11:46
  • Same thing, they also have only one line after `$v = strpad(...);` you have to use brackets only if you have more lines in same segment. If you have only one line after `foreach`, `for`, `if`, `while`, etc you can omit brackets. – Wh1T3h4Ck5 Apr 20 '12 at 11:47
3

I've been using this function for a while:

<?php

/**
 * Returns TRUE if given IPv4 address belongs to given network, FALSE otherwhise
 *
 * @param string $str_ip IP address in '127.0.0.1' format
 * @param string $str_range Network and mask as '127.0.0.0/8', '127.0.0.0/255.0.0.0' or '127.0.0.1'
 * @return bool
 *
 * @version v2011-08-30
 */
function ip_belongs_to_network($str_ip, $str_range){
    // Extract mask
    list($str_network, $str_mask) = array_pad(explode('/', $str_range), 2, NULL);
    if( is_null($str_mask) ){
        // No mask specified: range is a single IP address
        $mask = 0xFFFFFFFF;
    }elseif( (int)$str_mask==$str_mask ){
        // Mask is an integer: it's a bit count
        $mask = 0xFFFFFFFF << (32 - (int)$str_mask);
    }else{
        // Mask is in x.x.x.x format
        $mask = ip2long($str_mask);
    }

    $ip = ip2long($str_ip);
    $network = ip2long($str_network);
    $lower = $network & $mask;
    $upper = $network | (~$mask & 0xFFFFFFFF);

    return $ip>=$lower && $ip<=$upper;
}

Update:

Actually all the CIDR entries are in database in my case. How can I check an IP with all tose CIDR entries will a foreach loop can work?

It depends on how many entries you have. A PHP loop is fine for a couple of ranges, but having 50 of them would imply launching 50 SQL queries! In that case, you should probably switch to a database approach. E.g., you could store ranges in two columns (lower IP address and upper IP address). That way, all you need to do would be this:

$sql = 'SELECT COUNT(1) AS belongs_to_at_least_one_range
    FROM ranges
    WHERE :remote_address BETWEEN lower_address AND upper_address
    LIMIT 1';
$params = array(
    'remote_address' => ip2long($_SERVER['REMOTE_ADDR']),
);

(Or fetch all matches if you need that.)

can u please explain this line of code list($str_network, $str_mask) = array_pad(explode('/', $str_range), 2, NULL);

explode('/', $str_range)
// Split the string at `/` characters

array_pad(explode('/', $str_range), 2, NULL);
// Fill with NULLs if we have less than 2 items

list($str_network, $str_mask) =
// Store first part in `$str_network` and second part in `$str_mask`
Álvaro González
  • 142,137
  • 41
  • 261
  • 360
  • Your code is a bit complicated .... Actually all the CIDR entries are in database in my case. How can I check an IP with all tose CIDR entries will a foreach loop can work? and can u please explain this line of code list($str_network, $str_mask) = array_pad(explode('/', $str_range), 2, NULL); – Faryal Khan Apr 20 '12 at 09:41
  • Yeah, I needed to match all possible formats (not only CIDR) when I composed it. I'll update in a minute. – Álvaro González Apr 20 '12 at 09:46
  • what u are returing in this function? – Faryal Khan Apr 20 '12 at 10:00
  • Returns TRUE if given IPv4 address belongs to given network, FALSE otherwise – Álvaro González Apr 20 '12 at 10:12