检查CIDR子网是否包含IP地址

64

我正在寻找一种快速/简单的方法,将给定的IP4点分十进制IP与CIDR表示法掩码匹配。

我有一堆IP地址,需要查看它们是否匹配IP范围。

举个例子:

$ips = array('10.2.1.100', '10.2.1.101', '10.5.1.100', '1.2.3.4');

foreach ($ips as $IP) {
    if (cidr_match($IP, '10.2.0.0/16') == true) {
        print "you're in the 10.2 subnet\n"; 
    }
}

cidr_match()会是什么样子?

它不需要非常简单,但快速的话就最好了。使用只有内置或常用函数的任何东西都是一个奖励(因为我可能会得到一个人向我展示pear中做这个事情的东西,但我不能依赖于pear或该软件包在我的代码部署的地方被安装)。


有关处理IPv6地址,请参见https://dev59.com/F2sz5IYBdhLWcg3wWmjL - Sean the Bean
16个回答

1

请注意,Alnitak的答案适用于32/64位。

这是一个经过处理的版本,用于基于国家IP列表的快速垃圾邮件保护,您可以在任何地方获取。搜索国家IP列表或国家IP块(必须在此处提供一个:国家IP块生成器

将CIDR IP列表复制粘贴到字符串$cidrs中。并将此代码放置在页面HTML之前,可能在header.php文件中。

也可用于基于国家过滤页面模板中的AdSense使用。

这只是一种在半夜紧急情况下的解决方案。有时候我们需要为客户昨天快速提供这样的东西,所以这里就有了。

//++++++++++++++++++++++
//COUNTRY SPAM PROTECTOR
//speed: ~5ms @ 2000 cidrs
//comments start with #
//++++++++++++++++++++++
$cidrs=
'
#yourcountry
1.3.4.5/21
#mycountry
6.7.8.9/20
';
//$cidrs.="\n".'123.12.12.12/32';//test, your ip
$cidrs_ar=preg_split('/\s+/',$cidrs,-1,PREG_SPLIT_NO_EMPTY);
$ip=@$_SERVER['REMOTE_ADDR'];
$iplong=ip2long($ip);
//var_export($cidrs_ar);var_export($ip);var_export($iplong);
if($iplong)
  foreach($cidrs_ar as $cidr)
    {
    $ar=explode ('/', $cidr);
    $netiplong=ip2long($ar[0]);
    if($netiplong===false) continue;
    $mask=intval(@$ar[1]);
    if(!$mask) continue;
    $bitmask=-1 <<(32-$mask);
    if(($iplong & $bitmask) == ($netiplong & $bitmask))
        {
        header('Location: http://www.someotherwebsite.com/',true,303);
        exit;
        }
    }

1

也许对某些人有用。

将位掩码转换为IP掩码:

// convert 12 => 255.240.0.0
// ip2long('255.255.255.255') == -1
$ip = long2ip((-1 << (32 - $bit)) & -1);

将IP掩码转换为位掩码:
// convert 255.240.0.0 => 12

// is valid IP
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === false) {
    throw new \InvalidArgumentException(sprintf('Invalid IP "%s".', $ip));
}

// convert decimal to binary
$mask = '';
foreach (explode('.', $ip) as $octet) {
    $mask .= str_pad(decbin($octet), 8, '0', STR_PAD_LEFT);
}

// check mask
if (strpos('01', $mask) !== false) {
    // valid   11111111111111111111111100000000 -> 255.255.255.0
    // invalid 11111111111111111111111100000001 -> 255.255.255.1
    throw new \InvalidArgumentException(sprintf('IP mask "%s" is not valid.', $ip));
}

$bit = substr_count($mask, '1'); // bit mask

1

最终我采用了@A.R.NasirQureshi的解决方案,使用位对位匹配子网和掩码,并将其扩展以处理IPv6,因为没有其他解决方案提到了inet_pton,它可以有效地处理一些奇怪的情况,例如“2006:BCAA:64FF :: 104.121.140.212”,而不是按“.”或“:”拆分,这可能会很复杂!

function cidr_match($ip, $range){
  list($subnet, $bits) = explode('/', $range);

  //add 128-32=96 for IPv4 range bits
  if(filter_var($subnet, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)){ $bits += 96; }

  // compare bits of the 2 strings of 128 0 & 1 like 11000010101100
  $subnet = substr( IP2Bin($subnet), 0, $bits);
  $ip = substr( IP2Bin($ip), 0, $bits);

  return $ip == $subnet;
}
function IP2Bin($ip){
  $IPbin = ''; $bin = inet_pton($ip);
  if($bin===false){return str_repeat('0',128);} //in case of invalid IP

  //split into 4-char hexadecimal parts (just to avoid issues with hexdec for larger hex, but you can choose more)
  $arr = str_split( current( unpack('H*', $bin) ) , 4 );

  //each char is 4 bits so 4 chars == 16 bits
  foreach($arr as $p){ $IPbin .= sprintf("%016b", hexdec($p) ); }

  //always get 128 chars string of 1s & 0s
  return str_pad( $IPbin, 128, '0', STR_PAD_LEFT );
}

由于比较总是使用“128位”(又称为128个1和0)进行,因此无论IP和范围是否为相同类型(IPv4或IPv6),甚至当它们的类型不同时(IPv4与IPv6或反之亦然)都可以正常工作。

0

我想让你看看我的几行代码。之前其他人提供的示例似乎不起作用。其中一个原因是,据我所知,CIDR掩码位是二进制数字,因此位移必须在二进制数字上进行。我尝试将长IP转换为二进制,但遇到了最大二进制数限制。

好的,这是我的几行代码... 我期待你的评论。

function cidr_match($ipStr, $cidrStr) {

$ipStr = explode('.', $ipStr);
foreach ($ipStr as $key => $val) {
    $ipStr[$key] = str_pad(decbin($val), 8, '0', STR_PAD_LEFT);
    }
$ip = '';
foreach ($ipStr as $binval) {
    $ip = $ip . $binval;
    }

$cidrArr = explode('/',$cidrStr);

$maskIP = explode('.', $cidrArr[0]);
foreach ($maskIP as $key => $val) {
    $maskIP[$key] = str_pad(decbin($val), 8, '0', STR_PAD_LEFT);
    }
$maskIP = '';
foreach ($ipStr as $binval) {
    $maskIP = $maskIP . $binval;
    }
$maskBits = 32 - $cidrArr[1];
return (($ip>>$maskBits) == ($maskIP>>$maskBits));  
}

0
最近我需要将IP地址与网络列表匹配,并找到了这个问题。下面是一个变体,集成了之前发布的所有想法以及IPv6支持和地址、子网和掩码的验证。如果提交了不正确的CIDR格式或尝试将IPv4地址与IPv6网络(或反之)进行匹配,则该函数将返回false。
/*
  Checks if $ip belongs to $cidr.
  cidr_match('1.2.3.4', '1.2.3.5/24') == true
  cidr_match('::1.2.3.4', '::1.2.3.0/125') == true
  cidr_match('::1.2.3.4', '::1.2.3.0/126') == false
*/
function cidr_match($ip, $cidr)
{
    if (!filter_var($ip, FILTER_VALIDATE_IP)) return false;
    $p = unpack('N*', inet_pton($ip)); $ps = count($p);

    list ($subnet, $bits) = [$cidr, '128'];
    if (strstr($cidr, '/')) list ($subnet, $bits) = explode('/', $cidr, 2);
    if (!filter_var($subnet, FILTER_VALIDATE_IP)) return false;
    if (!preg_match('/^[1-9][0-9]*$/', $bits)) return false;
    $s = unpack('N*', inet_pton($subnet));
    if (count($s) != $ps) return false;
    $bits = intval($bits);

    $m = [];
    for ($i = 1; $i <= $ps; $i++)
        $m[$i] = ($i*32 - $bits) < 0 ? -1 : -1 << ($i*32 - $bits);

    for ($i = 1; $i <= $ps; $i++)
        if (($p[$i] & $m[$i]) != ($s[$i] & $m[$i]))
            return false;

    return true;
}

/*
  Returns first matching CIDR in $netlist or null if none match.
  net_match('1.2.3.4', ['1.2.3.5/24']) == '1.2.3.5/24'
  net_match('::1.2.3.4', ['::1.2.3.0/126', '::1.2.3.0/125']) ==  '::1.2.3.0/125'
*/
function net_match($ip, $netlist)
{
    foreach ($netlist as $cidr)
        if (cidr_match($ip, $cidr))
            return $cidr;
    return null;
}

0
重写Samuel Parkinson的代码,并添加输入验证检查和PHP8支持。
    /**
     * @param string $ip
     * @param string $cidr
     * @return bool
     * @link https://dev59.com/OnRB5IYBdhLWcg3wiHv7
     */
    private static function cidr_match(string $ip, string $cidr):bool
    {
        if (!filter_var($ip, FILTER_VALIDATE_IP))
        {
            throw new \InvalidArgumentException("The ip must be valid!");
        }
        if (!str_contains($cidr, '/'))
        {
            throw new \InvalidArgumentException("The cidr must contain a / ");
        }
        [$subnet, $mask] = explode('/', $cidr);
        $mask = (int)$mask;
        if (!filter_var($subnet, FILTER_VALIDATE_IP))
        {
            throw new \InvalidArgumentException("The subnet must be a valid IP!");
        }
        if ($mask < 0 || $mask > 32)
        {
            throw new \InvalidArgumentException("The cidr mask must be between 0 and 32!");
        }
        return (ip2long($ip) & ~((1 << (32 - $mask)) - 1)) == ip2long($subnet);
    }

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