确定给定的IP地址是否存在于CIDR中

9

我正在使用$_SERVER['REMOTE_ADDR']获取尝试访问我的网站的用户的IP地址。我在数据库中为每个盒子都有不同的IDS和不同的CIDR条目。我想检查用户IP是否存在于任何CIDR中,以及是否适用于特定的盒子。 如果存在,则允许他进入,否则不允许。 假设用户IP为

127.0.0.1

假设Box 12的CIDR条目为:
192.168.1.20/27
192.168.0.10/32

在数据库中的一列中存储了CIDR条目。

如果他使用的IP地址在此范围内,则应允许他进入网站,否则不允许。

我想检查他的IP地址是否与每个CIDR条目匹配。

请问有什么想法吗?

谢谢。


可能是在php5中将IP与CIDR掩码匹配?的重复问题。 - sberry
你尝试过什么? - ghoti
@ghoti 或许不是每个人都知道从哪里开始 - 我知道我当初并不知道。 - user336063
2个回答

12

好的,尝试按照以下5个简单步骤操作...

1. 将您的CIDR存储到数组中(从数据库中读取;想必您知道如何获取它们)

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

2. 获取用户的IP地址(远程地址)

$user_ip = $_SERVER['REMOTE_ADDR'];

3. 添加此函数

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. 将用户的IP地址与$cidrs进行比较。

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

5. 决定如何处理用户

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

就是这样!

这个函数的工作原理是将CIDR地址部分 (x.x.x.x) 转换为二进制字符串并取前N位。然后它对用户的IP执行相同的操作,并检查这些值是否匹配。

示例2(从函数中完整执行任务)

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;
  }

用法:

$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
  }

我正在从数据库中获取CIDR,我想检查所有的CIDR是否与用户的IP匹配。 - Faryal Khan
请更新您的问题并提供一些数据库示例(结构+数据)。 - Wh1T3h4Ck5
谢谢你的帮助。你能告诉我你的foreach循环在哪里结束吗? - Faryal Khan
我猜你在谈论第一个例子 foreach... if...。它的结束位置与 if 行相同。对于单行命令,您不需要使用大括号 { },而且该 foreach 之后只有一行 if。因此,我使用了 foreach() if() {...} 而不是 foreach() { if() {...} } - Wh1T3h4Ck5
IPvsCIDR函数中的foreach循环也不需要任何括号,这怎么样? - Faryal Khan
同样的事情,他们在 $v = strpad(...); 之后也只有一行,如果在同一段落中有多行,则必须使用括号。如果在 foreachforifwhile 等语句之后只有一行,则可以省略括号。 - Wh1T3h4Ck5

3

我已经使用这个函数有一段时间了:

<?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;
}

更新:

实际上,在我这种情况下,所有的CIDR条目都在数据库中。如何使用foreach循环检查具有所有这些CIDR条目的IP地址?

这取决于您有多少条目。对于几个范围,PHP循环是可以的,但是如果有50个范围,则意味着要启动50个SQL查询!在这种情况下,您应该考虑切换到数据库方法。例如,您可以将范围存储在两个列中(较低的IP地址和较高的IP地址)。那样,您需要做的就是这样:

$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']),
);

请解释这行代码: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`

你的代码有点复杂...实际上,在我的情况下,所有的CIDR条目都在数据库中。我该如何使用foreach循环检查一个IP地址和所有这些CIDR条目?您能解释一下这行代码吗?list($str_network, $str_mask) = array_pad(explode('/', $str_range), 2, NULL); - Faryal Khan
是的,当我编写它时,我需要匹配所有可能的格式(不仅仅是CIDR)。我马上更新。 - Álvaro González
这个函数你返回了什么? - Faryal Khan
如果给定的IPv4地址属于给定的网络,则返回TRUE,否则返回FALSE。 - Álvaro González

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接