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

发布于 2024-07-14 05:19:15 字数 492 浏览 9 评论 0原文

我正在寻找快速/简单的方法来将给定的 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 或安装在我的代码所在的位置的软件包部署)。

I'm looking for quick/simple method for matching a given IP4 dotted quad IP to a CIDR notation mask.

I have a bunch of IPs I need to see if they match a range of IPs.

example:

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

What would cidr_match() look like?

It doesn't really have to be simple, but fast would be good. Anything that uses only built-in/common functions is a bonus (as I'm likely to get one person to show me something in pear that does this, but I can't depend on pear or that package being installed where my code is deployed).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(16

2024-07-21 05:19:15

如果仅使用 IPv4:

  • 使用 ip2long() 将 IP 和子网范围转换为长整数
  • 将 /xx 转换为子网掩码
  • 执行按位“与”(即 ip 和掩码)”和检查“结果=子网”,

类似这样的东西应该有效:

function cidr_match($ip, $range)
{
    list ($subnet, $bits) = explode('/', $range);
    if ($bits === null) {
        $bits = 32;
    }
    $ip = ip2long($ip);
    $subnet = ip2long($subnet);
    $mask = -1 << (32 - $bits);
    $subnet &= $mask; # nb: in case the supplied subnet wasn't correctly aligned
    return ($ip & $mask) == $subnet;
}

If only using IPv4:

  • use ip2long() to convert the IPs and the subnet range into long integers
  • convert the /xx into a subnet mask
  • do a bitwise 'and' (i.e. ip & mask)' and check that that 'result = subnet'

something like this should work:

function cidr_match($ip, $range)
{
    list ($subnet, $bits) = explode('/', $range);
    if ($bits === null) {
        $bits = 32;
    }
    $ip = ip2long($ip);
    $subnet = ip2long($subnet);
    $mask = -1 << (32 - $bits);
    $subnet &= $mask; # nb: in case the supplied subnet wasn't correctly aligned
    return ($ip & $mask) == $subnet;
}
仄言 2024-07-21 05:19:15

在类似的情况下,我最终使用了 symfony/http-foundation。

使用此包时,您的代码将如下所示:

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

foreach($ips as $IP) {
    if (\Symfony\Component\HttpFoundation\IpUtils::checkIp($IP, '10.2.0.0/16')) {
        print "you're in the 10.2 subnet\n";
    }
}

它还处理 IPv6。

链接:https://packagist.org/packages/symfony/http-foundation

In a similar situation, I ended up using symfony/http-foundation.

When using this package, your code would look like:

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

foreach($ips as $IP) {
    if (\Symfony\Component\HttpFoundation\IpUtils::checkIp($IP, '10.2.0.0/16')) {
        print "you're in the 10.2 subnet\n";
    }
}

It also handles IPv6.

Link: https://packagist.org/packages/symfony/http-foundation

恋竹姑娘 2024-07-21 05:19:15

我发现许多这些方法在 PHP 5.2 之后就被破坏了。 但是,以下解决方案适用于版本 5.2 及更高版本:

function cidr_match($ip, $cidr)
{
    list($subnet, $mask) = explode('/', $cidr);

    if ((ip2long($ip) & ~((1 << (32 - $mask)) - 1) ) == ip2long($subnet))
    { 
        return true;
    }

    return false;
}

结果示例

cidr_match("1.2.3.4", "0.0.0.0/0"):         true
cidr_match("127.0.0.1", "127.0.0.1/32"):    true
cidr_match("127.0.0.1", "127.0.0.2/32"):    false

来源 http://www.php.net/manual/en/function.ip2long.php#82397

I found many of these methods breaking after PHP 5.2. However the following solution works on versions 5.2 and above:

function cidr_match($ip, $cidr)
{
    list($subnet, $mask) = explode('/', $cidr);

    if ((ip2long($ip) & ~((1 << (32 - $mask)) - 1) ) == ip2long($subnet))
    { 
        return true;
    }

    return false;
}

Example results

cidr_match("1.2.3.4", "0.0.0.0/0"):         true
cidr_match("127.0.0.1", "127.0.0.1/32"):    true
cidr_match("127.0.0.1", "127.0.0.2/32"):    false

Source http://www.php.net/manual/en/function.ip2long.php#82397.

睫毛上残留的泪 2024-07-21 05:19:15

一些功能发生了变化:

  • split和explode

function cidr_match($ip, $range)
{
    list ($subnet, $bits) = explode('/', $range);
    $ip = ip2long($ip);
    $subnet = ip2long($subnet);
    $mask = -1 << (32 - $bits);
    $subnet &= $mask; 
    return ($ip & $mask) == $subnet;
}

Some function changed:

  • split with explode

function cidr_match($ip, $range)
{
    list ($subnet, $bits) = explode('/', $range);
    $ip = ip2long($ip);
    $subnet = ip2long($subnet);
    $mask = -1 << (32 - $bits);
    $subnet &= $mask; 
    return ($ip & $mask) == $subnet;
}
是伱的 2024-07-21 05:19:15

这是一个快速的 64 位函数来完成此操作,请注释掉您不需要的返回行。 接受带有或不带有有效 CIDR 路由前缀的任何有效 Ipv4,例如 63.161.156.0/24 或 63.161.156.0

<?php
function cidr2range($ipv4){
if ($ip=strpos($ipv4,'/'))
{$n_ip=(1<<(32-substr($ipv4,1+$ip)))-1;   $ip_dec=ip2long(substr($ipv4,0,$ip)); }
else
{$n_ip=0;                                   $ip_dec=ip2long($ipv4);             }
$ip_min=$ip_dec&~$n_ip;
$ip_max=$ip_min+$n_ip;
#Array(2) of Decimal Values Range
return [$ip_min,$ip_max];
#Array(2) of Ipv4 Human Readable Range
return [long2ip($ip_min),long2ip($ip_max)];
#Array(2) of Ipv4 and Subnet Range
return [long2ip($ip_min),long2ip(~$n_ip)];
#Array(2) of Ipv4 and Wildcard Bits
return [long2ip($ip_min),long2ip($n_ip)];
#Integer Number of Ipv4 in Range
return ++$n_ip;
}

要快速检查给定 ipv4 是否与给定 CIDR 匹配,您可以像本例一样内联执行

<?php
$given_cidr='55.55.55.0/24';
$given_ipv4='55.55.55.55';
if(($range=cidr2range($given_cidr)) &&
($check=ip2long($given_ipv4))!==false &&
$check>=$range[0] && $check<=$range[1])
{
echo 'Yes, '.$given_ipv4.' is included in '.$given_cidr;
}
else
{
echo 'No, '.$given_ipv4.' is not included in '.$given_cidr;
}

获取完整范围作为给定 IP(有或没有 CIDR 路由前缀)的数组,您可以使用以下代码,但要小心,因为例如 25.25.25.25/ 16 返回一个包含 65536 个元素的数组,使用较小的路由前缀很容易耗尽内存

<?php
$result=cidr2range($ipv4);
for($ip_dec=$result[0];$ip_dec<=$result[1];$ip_dec++)
$full_range[$ip_dec]=long2ip($ip_dec);
print_r($full_range);

给定的 IP 数组匹配(带或不带 CIDR 路由前缀)

<?php
#This code is checking if a given ip belongs to googlebot
$given_ipv4='74.125.61.208';
$given_cidr_array=['108.59.93.43/32','108.59.93.40/31','108.59.93.44/30','108.59.93.32/29','108.59.93.48/28','108.59.93.0/27','108.59.93.64/26','108.59.93.192/26','108.59.92.192/27','108.59.92.128/26','108.59.92.96/27','108.59.92.0/27','108.59.94.208/29','108.59.94.192/28','108.59.94.240/28','108.59.94.128/26','108.59.94.16/29','108.59.94.0/28','108.59.94.32/27','108.59.94.64/26','108.59.95.0/24','108.59.88.0/22','108.59.81.0/27','108.59.80.0/24','108.59.82.0/23','108.59.84.0/22','108.170.217.128/28','108.170.217.160/27','108.170.217.192/26','108.170.217.0/25','108.170.216.0/24','108.170.218.0/23','108.170.220.0/22','108.170.208.0/21','108.170.192.0/20','108.170.224.0/19','108.177.0.0/17','104.132.0.0/14','104.154.0.0/15','104.196.0.0/14','107.167.160.0/19','107.178.192.0/18','125.17.82.112/30','125.16.7.72/30','74.125.0.0/16','72.14.192.0/18','77.109.131.208/28','77.67.50.32/27','66.102.0.0/20','66.227.77.144/29','66.249.64.0/19','67.148.177.136/29','64.124.98.104/29','64.71.148.240/29','64.68.64.64/26','64.68.80.0/20','64.41.221.192/28','64.41.146.208/28','64.9.224.0/19','64.233.160.0/19','65.171.1.144/28','65.170.13.0/28','65.167.144.64/28','65.220.13.0/24','65.216.183.0/24','70.32.132.0/23','70.32.128.0/22','70.32.136.0/21','70.32.144.0/20','85.182.250.128/26','85.182.250.0/25','80.239.168.192/26','80.149.20.0/25','61.246.224.136/30','61.246.190.124/30','63.237.119.112/29','63.226.245.56/29','63.158.137.224/29','63.166.17.128/25','63.161.156.0/24','63.88.22.0/23','41.206.188.128/26','12.234.149.240/29','12.216.80.0/24','8.34.217.24/29','8.34.217.0/28','8.34.217.32/27','8.34.217.64/26','8.34.217.128/25','8.34.216.0/24','8.34.218.0/23','8.34.220.0/22','8.34.208.128/29','8.34.208.144/28','8.34.208.160/27','8.34.208.192/26','8.34.208.0/25','8.34.209.0/24','8.34.210.0/23','8.34.212.0/22','8.35.195.128/28','8.35.195.160/27','8.35.195.192/26','8.35.195.0/25','8.35.194.0/24','8.35.192.0/23','8.35.196.0/22','8.35.200.0/21','8.8.8.0/24','8.8.4.0/24','8.6.48.0/21','4.3.2.0/24','23.236.48.0/20','23.251.128.0/19','216.239.32.0/19','216.252.220.0/22','216.136.145.128/27','216.33.229.160/29','216.33.229.144/29','216.34.7.176/28','216.58.192.0/19','216.109.75.80/28','216.74.130.48/28','216.74.153.0/27','217.118.234.96/28','208.46.199.160/29','208.44.48.240/29','208.21.209.0/28','208.184.125.240/28','209.185.108.128/25','209.85.128.0/17','213.200.103.128/26','213.200.99.192/26','213.155.151.128/26','199.192.112.224/29','199.192.112.192/27','199.192.112.128/26','199.192.112.0/25','199.192.113.176/28','199.192.113.128/27','199.192.113.192/26','199.192.113.0/25','199.192.115.80/28','199.192.115.96/27','199.192.115.0/28','199.192.115.128/25','199.192.114.192/26','199.192.114.0/25','199.223.232.0/21','198.108.100.192/28','195.16.45.144/29','192.104.160.0/23','192.158.28.0/22','192.178.0.0/15','206.160.135.240/28','207.223.160.0/20','203.222.167.144/28','173.255.125.72/29','173.255.125.80/28','173.255.125.96/27','173.255.125.0/27','173.255.125.128/25','173.255.124.240/29','173.255.124.232/29','173.255.124.192/27','173.255.124.128/29','173.255.124.144/28','173.255.124.160/27','173.255.124.48/29','173.255.124.32/28','173.255.124.0/27','173.255.124.64/26','173.255.126.0/23','173.255.122.128/26','173.255.122.64/26','173.255.123.0/24','173.255.121.128/26','173.255.121.0/25','173.255.120.0/24','173.255.117.32/27','173.255.117.64/26','173.255.117.128/25','173.255.116.192/27','173.255.116.128/26','173.255.116.0/25','173.255.118.0/23','173.255.112.0/22','173.194.0.0/16','172.102.8.0/21','172.253.0.0/16','172.217.0.0/16','162.216.148.0/22','162.222.176.0/21','180.87.33.64/26','128.177.109.0/26','128.177.119.128/25','128.177.163.0/25','130.211.0.0/16','142.250.0.0/15','146.148.0.0/17'];
echo '<pre>';
$in_range=false;
if (($given_ipv4_dec=ip2long($given_ipv4))!==false)
{
foreach($given_cidr_array as $given_cidr){
if(($range=cidr2range($given_cidr)) &&
$given_ipv4_dec>=$range[0] && $given_ipv4_dec<=$range[1])
{
$in_range=true;
echo $given_ipv4.' matched '.$given_cidr.' ('.join(array_map('long2ip',$range),' - ').")\n";
}
}
}
echo $given_ipv4.' is probably'.($in_range?'':' not').' a Googlebot IP';

快速检查给定的 ipv4 是否与 运行速度快,该函数不检查输入,但形式上它应该是与以下正则表达式匹配的字符串

#^(?:((?:0)|(?:2(?:(?:[0-4][0-9])|(?:5[0-5])))|(?:1?[0-9]{1,2}))\.((?:0)|(?:2(?:(?:[0-4][0-9])|(?:5[0-5])))|(?:1?[0-9]{1,2}))\.((?:0)|(?:2(?:(?:[0-4][0-9])|(?:5[0-5])))|(?:1?[0-9]{1,2}))\.((?:0)|(?:2(?:(?:[0-4][0-9])|(?:5[0-5])))|(?:1?[0-9]{1,2}))(?:/((?:(?:0)|(?:3[0-2])|(?:[1-2]?[0-9]))))?)$#

如果您想在使用该函数之前验证输入

<?php
if (is_string($ipv4) && preg_match('#^(?:((?:0)|(?:2(?:(?:[0-4][0-9])|(?:5[0-5])))|(?:1?[0-9]{1,2}))\.((?:0)|(?:2(?:(?:[0-4][0-9])|(?:5[0-5])))|(?:1?[0-9]{1,2}))\.((?:0)|(?:2(?:(?:[0-4][0-9])|(?:5[0-5])))|(?:1?[0-9]{1,2}))\.((?:0)|(?:2(?:(?:[0-4][0-9])|(?:5[0-5])))|(?:1?[0-9]{1,2}))(?:/((?:(?:0)|(?:3[0-2])|(?:[1-2]?[0-9]))))?)$#',$ipv4))
{
#This is a valid ipv4 with or without CIDR Routing Prefix
$result=cidr2range($ipv4);
print_r($result);
}

那么正式问题的答案如下

<?php
#Requiring cidr2range shown above function
function cidr_match($mixed_ip,$mixed_cidr){
if (!is_array($mixed_ip)){
$string_mode=true;
$mixed_ip=[$mixed_ip=>0];
}
else $mixed_ip=array_fill_keys($mixed_ip,0);
if (!is_array($mixed_cidr)) $mixed_cidr=[$mixed_cidr];
foreach($mixed_ip   as $ip => &$result)
foreach($mixed_cidr as $cidr)
{
if(($range=cidr2range($cidr)) &&
($check=ip2long($ip))!==false &&
$check>=$range[0] && $check<=$range[1]){
$result=$cidr;
break;
}
}
$mixed_ip=array_filter($mixed_ip);
return $string_mode?($mixed_ip?true:false):$mixed_ip;
}

print '<pre>';

#Your example
$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"; 
    }
}


#Also working with IP array and/or CIDR array
#If IP array is given then return an array containing IP (keys) matching CIDR (values)
$result=cidr_match($ips,['20.2.0.0/16','10.2.0.0/15']);
foreach($result as $ip => $cidr){
print "$ip is in the $cidr subnet\n"; 
}

您可以使用这些示例编译自己的函数,希望这几行代码对您有所帮助......

Here is one fast 64bits function to do it, please comment the return line you don't need. Accepting any valid Ipv4 with or without valid CIDR Routing Prefix for example 63.161.156.0/24 or 63.161.156.0

<?php
function cidr2range($ipv4){
if ($ip=strpos($ipv4,'/'))
{$n_ip=(1<<(32-substr($ipv4,1+$ip)))-1;   $ip_dec=ip2long(substr($ipv4,0,$ip)); }
else
{$n_ip=0;                                   $ip_dec=ip2long($ipv4);             }
$ip_min=$ip_dec&~$n_ip;
$ip_max=$ip_min+$n_ip;
#Array(2) of Decimal Values Range
return [$ip_min,$ip_max];
#Array(2) of Ipv4 Human Readable Range
return [long2ip($ip_min),long2ip($ip_max)];
#Array(2) of Ipv4 and Subnet Range
return [long2ip($ip_min),long2ip(~$n_ip)];
#Array(2) of Ipv4 and Wildcard Bits
return [long2ip($ip_min),long2ip($n_ip)];
#Integer Number of Ipv4 in Range
return ++$n_ip;
}

To fast check if a given ipv4 is matching a given CIDR you can do it inline like in this example

<?php
$given_cidr='55.55.55.0/24';
$given_ipv4='55.55.55.55';
if(($range=cidr2range($given_cidr)) &&
($check=ip2long($given_ipv4))!==false &&
$check>=$range[0] && $check<=$range[1])
{
echo 'Yes, '.$given_ipv4.' is included in '.$given_cidr;
}
else
{
echo 'No, '.$given_ipv4.' is not included in '.$given_cidr;
}

To get the full range as an array for a given IP (with or without CIDR Routing Prefix) you can use the following code but be carefull because for example 25.25.25.25/16 return an array with 65536 elements and you can easily run out of memory using a smaller Routing Prefix

<?php
$result=cidr2range($ipv4);
for($ip_dec=$result[0];$ip_dec<=$result[1];$ip_dec++)
$full_range[$ip_dec]=long2ip($ip_dec);
print_r($full_range);

To fast check if a given ipv4 is matching a given array of IP (with or without CIDR Routing Prefix)

<?php
#This code is checking if a given ip belongs to googlebot
$given_ipv4='74.125.61.208';
$given_cidr_array=['108.59.93.43/32','108.59.93.40/31','108.59.93.44/30','108.59.93.32/29','108.59.93.48/28','108.59.93.0/27','108.59.93.64/26','108.59.93.192/26','108.59.92.192/27','108.59.92.128/26','108.59.92.96/27','108.59.92.0/27','108.59.94.208/29','108.59.94.192/28','108.59.94.240/28','108.59.94.128/26','108.59.94.16/29','108.59.94.0/28','108.59.94.32/27','108.59.94.64/26','108.59.95.0/24','108.59.88.0/22','108.59.81.0/27','108.59.80.0/24','108.59.82.0/23','108.59.84.0/22','108.170.217.128/28','108.170.217.160/27','108.170.217.192/26','108.170.217.0/25','108.170.216.0/24','108.170.218.0/23','108.170.220.0/22','108.170.208.0/21','108.170.192.0/20','108.170.224.0/19','108.177.0.0/17','104.132.0.0/14','104.154.0.0/15','104.196.0.0/14','107.167.160.0/19','107.178.192.0/18','125.17.82.112/30','125.16.7.72/30','74.125.0.0/16','72.14.192.0/18','77.109.131.208/28','77.67.50.32/27','66.102.0.0/20','66.227.77.144/29','66.249.64.0/19','67.148.177.136/29','64.124.98.104/29','64.71.148.240/29','64.68.64.64/26','64.68.80.0/20','64.41.221.192/28','64.41.146.208/28','64.9.224.0/19','64.233.160.0/19','65.171.1.144/28','65.170.13.0/28','65.167.144.64/28','65.220.13.0/24','65.216.183.0/24','70.32.132.0/23','70.32.128.0/22','70.32.136.0/21','70.32.144.0/20','85.182.250.128/26','85.182.250.0/25','80.239.168.192/26','80.149.20.0/25','61.246.224.136/30','61.246.190.124/30','63.237.119.112/29','63.226.245.56/29','63.158.137.224/29','63.166.17.128/25','63.161.156.0/24','63.88.22.0/23','41.206.188.128/26','12.234.149.240/29','12.216.80.0/24','8.34.217.24/29','8.34.217.0/28','8.34.217.32/27','8.34.217.64/26','8.34.217.128/25','8.34.216.0/24','8.34.218.0/23','8.34.220.0/22','8.34.208.128/29','8.34.208.144/28','8.34.208.160/27','8.34.208.192/26','8.34.208.0/25','8.34.209.0/24','8.34.210.0/23','8.34.212.0/22','8.35.195.128/28','8.35.195.160/27','8.35.195.192/26','8.35.195.0/25','8.35.194.0/24','8.35.192.0/23','8.35.196.0/22','8.35.200.0/21','8.8.8.0/24','8.8.4.0/24','8.6.48.0/21','4.3.2.0/24','23.236.48.0/20','23.251.128.0/19','216.239.32.0/19','216.252.220.0/22','216.136.145.128/27','216.33.229.160/29','216.33.229.144/29','216.34.7.176/28','216.58.192.0/19','216.109.75.80/28','216.74.130.48/28','216.74.153.0/27','217.118.234.96/28','208.46.199.160/29','208.44.48.240/29','208.21.209.0/28','208.184.125.240/28','209.185.108.128/25','209.85.128.0/17','213.200.103.128/26','213.200.99.192/26','213.155.151.128/26','199.192.112.224/29','199.192.112.192/27','199.192.112.128/26','199.192.112.0/25','199.192.113.176/28','199.192.113.128/27','199.192.113.192/26','199.192.113.0/25','199.192.115.80/28','199.192.115.96/27','199.192.115.0/28','199.192.115.128/25','199.192.114.192/26','199.192.114.0/25','199.223.232.0/21','198.108.100.192/28','195.16.45.144/29','192.104.160.0/23','192.158.28.0/22','192.178.0.0/15','206.160.135.240/28','207.223.160.0/20','203.222.167.144/28','173.255.125.72/29','173.255.125.80/28','173.255.125.96/27','173.255.125.0/27','173.255.125.128/25','173.255.124.240/29','173.255.124.232/29','173.255.124.192/27','173.255.124.128/29','173.255.124.144/28','173.255.124.160/27','173.255.124.48/29','173.255.124.32/28','173.255.124.0/27','173.255.124.64/26','173.255.126.0/23','173.255.122.128/26','173.255.122.64/26','173.255.123.0/24','173.255.121.128/26','173.255.121.0/25','173.255.120.0/24','173.255.117.32/27','173.255.117.64/26','173.255.117.128/25','173.255.116.192/27','173.255.116.128/26','173.255.116.0/25','173.255.118.0/23','173.255.112.0/22','173.194.0.0/16','172.102.8.0/21','172.253.0.0/16','172.217.0.0/16','162.216.148.0/22','162.222.176.0/21','180.87.33.64/26','128.177.109.0/26','128.177.119.128/25','128.177.163.0/25','130.211.0.0/16','142.250.0.0/15','146.148.0.0/17'];
echo '<pre>';
$in_range=false;
if (($given_ipv4_dec=ip2long($given_ipv4))!==false)
{
foreach($given_cidr_array as $given_cidr){
if(($range=cidr2range($given_cidr)) &&
$given_ipv4_dec>=$range[0] && $given_ipv4_dec<=$range[1])
{
$in_range=true;
echo $given_ipv4.' matched '.$given_cidr.' ('.join(array_map('long2ip',$range),' - ').")\n";
}
}
}
echo $given_ipv4.' is probably'.($in_range?'':' not').' a Googlebot IP';

To run fast the function don't check input but formally it should be a string matching the following regex

#^(?:((?:0)|(?:2(?:(?:[0-4][0-9])|(?:5[0-5])))|(?:1?[0-9]{1,2}))\.((?:0)|(?:2(?:(?:[0-4][0-9])|(?:5[0-5])))|(?:1?[0-9]{1,2}))\.((?:0)|(?:2(?:(?:[0-4][0-9])|(?:5[0-5])))|(?:1?[0-9]{1,2}))\.((?:0)|(?:2(?:(?:[0-4][0-9])|(?:5[0-5])))|(?:1?[0-9]{1,2}))(?:/((?:(?:0)|(?:3[0-2])|(?:[1-2]?[0-9]))))?)$#

If you want to verify the input before using the function

<?php
if (is_string($ipv4) && preg_match('#^(?:((?:0)|(?:2(?:(?:[0-4][0-9])|(?:5[0-5])))|(?:1?[0-9]{1,2}))\.((?:0)|(?:2(?:(?:[0-4][0-9])|(?:5[0-5])))|(?:1?[0-9]{1,2}))\.((?:0)|(?:2(?:(?:[0-4][0-9])|(?:5[0-5])))|(?:1?[0-9]{1,2}))\.((?:0)|(?:2(?:(?:[0-4][0-9])|(?:5[0-5])))|(?:1?[0-9]{1,2}))(?:/((?:(?:0)|(?:3[0-2])|(?:[1-2]?[0-9]))))?)$#',$ipv4))
{
#This is a valid ipv4 with or without CIDR Routing Prefix
$result=cidr2range($ipv4);
print_r($result);
}

Then the formal answer to your question is the following

<?php
#Requiring cidr2range shown above function
function cidr_match($mixed_ip,$mixed_cidr){
if (!is_array($mixed_ip)){
$string_mode=true;
$mixed_ip=[$mixed_ip=>0];
}
else $mixed_ip=array_fill_keys($mixed_ip,0);
if (!is_array($mixed_cidr)) $mixed_cidr=[$mixed_cidr];
foreach($mixed_ip   as $ip => &$result)
foreach($mixed_cidr as $cidr)
{
if(($range=cidr2range($cidr)) &&
($check=ip2long($ip))!==false &&
$check>=$range[0] && $check<=$range[1]){
$result=$cidr;
break;
}
}
$mixed_ip=array_filter($mixed_ip);
return $string_mode?($mixed_ip?true:false):$mixed_ip;
}

print '<pre>';

#Your example
$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"; 
    }
}


#Also working with IP array and/or CIDR array
#If IP array is given then return an array containing IP (keys) matching CIDR (values)
$result=cidr_match($ips,['20.2.0.0/16','10.2.0.0/15']);
foreach($result as $ip => $cidr){
print "$ip is in the $cidr subnet\n"; 
}

You can compile your own function using these examples, hope these few lines have helped you…

兔姬 2024-07-21 05:19:15

我的技术使用子网和掩码进行位对位匹配。

function cidr_match($ip, $range){
    list ($subnet, $bits) = explode('/', $range);
    $ip = substr(IP2bin($ip),0,$bits) ;
    $subnet = substr(IP2Bin($subnet),0,$bits) ;
    return ($ip == $subnet) ;
}

function IP2Bin($ip){
    $ipbin = '';
    $ips = explode(".",$ip) ;
    foreach ($ips as $iptmp){
        $ipbin .= sprintf("%08b",$iptmp) ;
    }
    return $ipbin ;
}

My technique uses bit to bit matching using subnet and mask.

function cidr_match($ip, $range){
    list ($subnet, $bits) = explode('/', $range);
    $ip = substr(IP2bin($ip),0,$bits) ;
    $subnet = substr(IP2Bin($subnet),0,$bits) ;
    return ($ip == $subnet) ;
}

function IP2Bin($ip){
    $ipbin = '';
    $ips = explode(".",$ip) ;
    foreach ($ips as $iptmp){
        $ipbin .= sprintf("%08b",$iptmp) ;
    }
    return $ipbin ;
}
迟到的我 2024-07-21 05:19:15

我还需要根据 CIDR 掩码测试 IP。 我发现一个网站有很好的解释和源代码,运行得很好。

网站 http://pgregg.com/blog/2009/04/php-algorithms-determining-if-an-ip-is-within-a-specific-range/

因为该网站有一天可能不复存在,这是代码

<?php

/*
 * ip_in_range.php - Function to determine if an IP is located in a
 *                   specific range as specified via several alternative
 *                   formats.
 *
 * Network ranges can be specified as:
 * 1. Wildcard format:     1.2.3.*
 * 2. CIDR format:         1.2.3/24  OR  1.2.3.4/255.255.255.0
 * 3. Start-End IP format: 1.2.3.0-1.2.3.255
 *
 * Return value BOOLEAN : ip_in_range($ip, $range);
 *
 * Copyright 2008: Paul Gregg <[email protected]>
 * 10 January 2008
 * Version: 1.2
 *
 * Source website: http://www.pgregg.com/projects/php/ip_in_range/
 * Version 1.2
 *
 * This software is Donationware - if you feel you have benefited from
 * the use of this tool then please consider a donation. The value of
 * which is entirely left up to your discretion.
 * http://www.pgregg.com/donate/
 *
 * Please do not remove this header, or source attibution from this file.
 */


// decbin32
// In order to simplify working with IP addresses (in binary) and their
// netmasks, it is easier to ensure that the binary strings are padded
// with zeros out to 32 characters - IP addresses are 32 bit numbers
Function decbin32 ($dec) {
  return str_pad(decbin($dec), 32, '0', STR_PAD_LEFT);
}

// ip_in_range
// This function takes 2 arguments, an IP address and a "range" in several
// different formats.
// Network ranges can be specified as:
// 1. Wildcard format:     1.2.3.*
// 2. CIDR format:         1.2.3/24  OR  1.2.3.4/255.255.255.0
// 3. Start-End IP format: 1.2.3.0-1.2.3.255
// The function will return true if the supplied IP is within the range.
// Note little validation is done on the range inputs - it expects you to
// use one of the above 3 formats.
Function ip_in_range($ip, $range) {
  if (strpos($range, '/') !== false) {
    // $range is in IP/NETMASK format
    list($range, $netmask) = explode('/', $range, 2);
    if (strpos($netmask, '.') !== false) {
      // $netmask is a 255.255.0.0 format
      $netmask = str_replace('*', '0', $netmask);
      $netmask_dec = ip2long($netmask);
      return ( (ip2long($ip) & $netmask_dec) == (ip2long($range) & $netmask_dec) );
    } else {
      // $netmask is a CIDR size block
      // fix the range argument
      $x = explode('.', $range);
      while(count($x)<4) $x[] = '0';
      list($a,$b,$c,$d) = $x;
      $range = sprintf("%u.%u.%u.%u", empty($a)?'0':$a, empty($b)?'0':$b,empty($c)?'0':$c,empty($d)?'0':$d);
      $range_dec = ip2long($range);
      $ip_dec = ip2long($ip);

      # Strategy 1 - Create the netmask with 'netmask' 1s and then fill it to 32 with 0s
      #$netmask_dec = bindec(str_pad('', $netmask, '1') . str_pad('', 32-$netmask, '0'));

      # Strategy 2 - Use math to create it
      $wildcard_dec = pow(2, (32-$netmask)) - 1;
      $netmask_dec = ~ $wildcard_dec;

      return (($ip_dec & $netmask_dec) == ($range_dec & $netmask_dec));
    }
  } else {
    // range might be 255.255.*.* or 1.2.3.0-1.2.3.255
    if (strpos($range, '*') !==false) { // a.b.*.* format
      // Just convert to A-B format by setting * to 0 for A and 255 for B
      $lower = str_replace('*', '0', $range);
      $upper = str_replace('*', '255', $range);
      $range = "$lower-$upper";
    }

    if (strpos($range, '-')!==false) { // A-B format
      list($lower, $upper) = explode('-', $range, 2);
      $lower_dec = (float)sprintf("%u",ip2long($lower));
      $upper_dec = (float)sprintf("%u",ip2long($upper));
      $ip_dec = (float)sprintf("%u",ip2long($ip));
      return ( ($ip_dec>=$lower_dec) && ($ip_dec<=$upper_dec) );
    }

    echo 'Range argument is not in 1.2.3.4/24 or 1.2.3.4/255.255.255.0 format';
    return false;
  }

}
?>

(我没有开发这个;这是由 Paul Gregg 开发的(http://pgregg.com/

I also needed to test IP's against CIDR masks. I've found a website with excellent explanation and sourcecode which works perfectly well.

The website http://pgregg.com/blog/2009/04/php-algorithms-determining-if-an-ip-is-within-a-specific-range/

Because the website can one day cease to exist, here is the code

<?php

/*
 * ip_in_range.php - Function to determine if an IP is located in a
 *                   specific range as specified via several alternative
 *                   formats.
 *
 * Network ranges can be specified as:
 * 1. Wildcard format:     1.2.3.*
 * 2. CIDR format:         1.2.3/24  OR  1.2.3.4/255.255.255.0
 * 3. Start-End IP format: 1.2.3.0-1.2.3.255
 *
 * Return value BOOLEAN : ip_in_range($ip, $range);
 *
 * Copyright 2008: Paul Gregg <[email protected]>
 * 10 January 2008
 * Version: 1.2
 *
 * Source website: http://www.pgregg.com/projects/php/ip_in_range/
 * Version 1.2
 *
 * This software is Donationware - if you feel you have benefited from
 * the use of this tool then please consider a donation. The value of
 * which is entirely left up to your discretion.
 * http://www.pgregg.com/donate/
 *
 * Please do not remove this header, or source attibution from this file.
 */


// decbin32
// In order to simplify working with IP addresses (in binary) and their
// netmasks, it is easier to ensure that the binary strings are padded
// with zeros out to 32 characters - IP addresses are 32 bit numbers
Function decbin32 ($dec) {
  return str_pad(decbin($dec), 32, '0', STR_PAD_LEFT);
}

// ip_in_range
// This function takes 2 arguments, an IP address and a "range" in several
// different formats.
// Network ranges can be specified as:
// 1. Wildcard format:     1.2.3.*
// 2. CIDR format:         1.2.3/24  OR  1.2.3.4/255.255.255.0
// 3. Start-End IP format: 1.2.3.0-1.2.3.255
// The function will return true if the supplied IP is within the range.
// Note little validation is done on the range inputs - it expects you to
// use one of the above 3 formats.
Function ip_in_range($ip, $range) {
  if (strpos($range, '/') !== false) {
    // $range is in IP/NETMASK format
    list($range, $netmask) = explode('/', $range, 2);
    if (strpos($netmask, '.') !== false) {
      // $netmask is a 255.255.0.0 format
      $netmask = str_replace('*', '0', $netmask);
      $netmask_dec = ip2long($netmask);
      return ( (ip2long($ip) & $netmask_dec) == (ip2long($range) & $netmask_dec) );
    } else {
      // $netmask is a CIDR size block
      // fix the range argument
      $x = explode('.', $range);
      while(count($x)<4) $x[] = '0';
      list($a,$b,$c,$d) = $x;
      $range = sprintf("%u.%u.%u.%u", empty($a)?'0':$a, empty($b)?'0':$b,empty($c)?'0':$c,empty($d)?'0':$d);
      $range_dec = ip2long($range);
      $ip_dec = ip2long($ip);

      # Strategy 1 - Create the netmask with 'netmask' 1s and then fill it to 32 with 0s
      #$netmask_dec = bindec(str_pad('', $netmask, '1') . str_pad('', 32-$netmask, '0'));

      # Strategy 2 - Use math to create it
      $wildcard_dec = pow(2, (32-$netmask)) - 1;
      $netmask_dec = ~ $wildcard_dec;

      return (($ip_dec & $netmask_dec) == ($range_dec & $netmask_dec));
    }
  } else {
    // range might be 255.255.*.* or 1.2.3.0-1.2.3.255
    if (strpos($range, '*') !==false) { // a.b.*.* format
      // Just convert to A-B format by setting * to 0 for A and 255 for B
      $lower = str_replace('*', '0', $range);
      $upper = str_replace('*', '255', $range);
      $range = "$lower-$upper";
    }

    if (strpos($range, '-')!==false) { // A-B format
      list($lower, $upper) = explode('-', $range, 2);
      $lower_dec = (float)sprintf("%u",ip2long($lower));
      $upper_dec = (float)sprintf("%u",ip2long($upper));
      $ip_dec = (float)sprintf("%u",ip2long($ip));
      return ( ($ip_dec>=$lower_dec) && ($ip_dec<=$upper_dec) );
    }

    echo 'Range argument is not in 1.2.3.4/24 or 1.2.3.4/255.255.255.0 format';
    return false;
  }

}
?>

(I did not develop this; this is developed by Paul Gregg (http://pgregg.com/)

苦行僧 2024-07-21 05:19:15

我最近需要将 IP 地址与 CIDR 掩码进行匹配,并看到了这篇文章。 下面是基于上述想法的稍微不同的方法,包括对 CIDR 输入的检查。 如果提交的 CIDR 格式不正确,该函数将返回 false。

我为任何需要经过测试的交钥匙功能的人发布了此解决方案。

/**
 * Validates subnet specified by CIDR notation.of the form IP address followed by 
 * a '/' character and a decimal number specifying the length, in bits, of the subnet
 * mask or routing prefix (number from 0 to 32).
 *
 * @param $ip - IP address to check
 * @param $cidr - IP address range in CIDR notation for check
 * @return bool - true match found otherwise false
 */
function cidr_match($ip, $cidr) {
    $outcome = false;
    $pattern = '/^(([01]?\d?\d|2[0-4]\d|25[0-5])\.){3}([01]?\d?\d|2[0-4]\d|25[0-5])\/(\d{1}|[0-2]{1}\d{1}|3[0-2])$/';
    if (preg_match($pattern, $cidr)){
        list($subnet, $mask) = explode('/', $cidr);
        if (ip2long($ip) >> (32 - $mask) == ip2long($subnet) >> (32 - $mask)) {
            $outcome = true;
        }
    }
    return $outcome;
}

测试数据如下图所示:

测试结果 IP 检查

I recently needed to match an IP address to a CIDR mask and came across this article. Below is a slightly different approach based on the ideas above and includes a check on the CIDR input. The function returns false if an incorrect CIDR format is submitted.

I posted this solution for anyone who needs a turn-key function that has been tested.

/**
 * Validates subnet specified by CIDR notation.of the form IP address followed by 
 * a '/' character and a decimal number specifying the length, in bits, of the subnet
 * mask or routing prefix (number from 0 to 32).
 *
 * @param $ip - IP address to check
 * @param $cidr - IP address range in CIDR notation for check
 * @return bool - true match found otherwise false
 */
function cidr_match($ip, $cidr) {
    $outcome = false;
    $pattern = '/^(([01]?\d?\d|2[0-4]\d|25[0-5])\.){3}([01]?\d?\d|2[0-4]\d|25[0-5])\/(\d{1}|[0-2]{1}\d{1}|3[0-2])$/';
    if (preg_match($pattern, $cidr)){
        list($subnet, $mask) = explode('/', $cidr);
        if (ip2long($ip) >> (32 - $mask) == ip2long($subnet) >> (32 - $mask)) {
            $outcome = true;
        }
    }
    return $outcome;
}

Test data is shown in the image below:

Test Results IP Checks

月光色 2024-07-21 05:19:15
function cidr_match($ipStr, $cidrStr) {
  $ip = ip2long($ipStr);
  $cidrArr = split('/',$cidrStr);
  $maskIP = ip2long($cidrArr[0]);
  $maskBits = 32 - $cidrArr[1];
  return (($ip>>$maskBits) == ($maskIP>>$maskBits));
}
function cidr_match($ipStr, $cidrStr) {
  $ip = ip2long($ipStr);
  $cidrArr = split('/',$cidrStr);
  $maskIP = ip2long($cidrArr[0]);
  $maskBits = 32 - $cidrArr[1];
  return (($ip>>$maskBits) == ($maskIP>>$maskBits));
}
我乃一代侩神 2024-07-21 05:19:15

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

这是它的熟版,可根据随处可见的国家/地区 IP 列表进行快速垃圾邮件防护。 google 查找国家/地区 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;
        }
    }

Just a note, Alnitak's answer works 32/64 bit.

Here is a cooked version of it, for quick spam protection based on country IP lists that you can get everywhere. google for country ip list or country ip block (Have to give one here, really difficult to find it in that sites page navigation:Country ip block generator)

Copy-paste your cidr ip list to string $cidrs. And put this code just before page html, possibly in the header.php file.

Can also be used to filter adsense use in page templates based on country.

This is only a in-the-middle-of-the-night-urgency solution. Sometimes one needs to come up with something like this for a client quickly yesterday, so here it is.

//++++++++++++++++++++++
//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;
        }
    }
潦草背影 2024-07-21 05:19:15

您还可以使用 Net_IPv4 PEAR 库

function cidr_match($ip, $net){
  include_once("Net/IPv4.php");
  $objIP = new Net_IPv4();
  return $objIP->ipInNetwork($ip, $net);
}

You also can use Net_IPv4 PEAR library.

function cidr_match($ip, $net){
  include_once("Net/IPv4.php");
  $objIP = new Net_IPv4();
  return $objIP->ipInNetwork($ip, $net);
}
多彩岁月 2024-07-21 05:19:15

也许它对某人有用。

将位掩码转换为 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

Perhaps it is useful to someone.

Convert bit mask into IP mask:

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

Convert IP mask into bit mask:

// 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
溺ぐ爱和你が 2024-07-21 05:19:15

我最终使用了 @ARNasirQureshi 的解决方案,该解决方案具有逐位匹配的子网和掩码,我也将其扩展为处理 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 或反之亦然)。

I ended up using @A.R.NasirQureshi's solution with bit to bit matching subnet and mask, which I expanded to deal with IPv6 as well since no other solution mentioned inet_pton that effectively takes care of some odd cases like "2006:BCAA:64FF::104.121.140.212" , instead of splitting by "." or ":" which may be complicated !

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

Because the comparison always takes place with "128 bits" (aka 128 1s & 0s), this works whether both the IP and Range are the same type (IPv4 or IPv6), and even when their type is different (IPv4 vs IPv6 or vice-versa).

维持三分热 2024-07-21 05:19:15

我最近需要将 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;
}

I recently needed to match IP addresses to a network list and found this question. Below is a variant integrating all the ideas posted earlier plus IPv6 support and validation of address, subnet and mask. The function returns false if an incorrect CIDR format is submitted or when attempt to match IPv4 address to IPv6 network (or vice versa) is made.

/*
  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;
}
扛起拖把扫天下 2024-07-21 05:19:15

使用输入验证检查和 php8 重写塞缪尔帕金森代码

    /**
     * @param string $ip
     * @param string $cidr
     * @return bool
     * @link https://stackoverflow.com/questions/594112/check-whether-or-not-a-cidr-subnet-contains-an-ip-address
     */
    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);
    }

rewriting samuel parkinsons code with input validation checks and php8

    /**
     * @param string $ip
     * @param string $cidr
     * @return bool
     * @link https://stackoverflow.com/questions/594112/check-whether-or-not-a-cidr-subnet-contains-an-ip-address
     */
    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);
    }
吃颗糖壮壮胆 2024-07-21 05:19:15

我想让你看看我的几行字。 人们在我之前提出的例子似乎不起作用。 据我了解,原因之一是 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));
}

I want to have you look at my few lines. The examples that people suggested before me don't seem to work. One reason being, as far as I understand it, is that CIDR mask bits are binary numbers, so the bit shift must be done on a binary number. I have tried converting the long IP's into binaries, but ran into a max binary number limit.

OK, here my few lines:

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));
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文