有没有一种使用 CIDR 表示法将 IPv6 地址与 IPv6 子网匹配的好方法?我正在寻找的是与此等效的 IPv6: 将 IP 与 PHP 5 中的 CIDR 掩码匹配?
无法使用上面给出的示例,因为 IPv6 地址的长度为 128 位,从而阻止了按位左移功能正常工作。你能想到别的办法吗?
编辑:将我自己的解决方案添加到答案列表中。
有没有一种使用 CIDR 表示法将 IPv6 地址与 IPv6 子网匹配的好方法?我正在寻找的是与此等效的 IPv6: 将 IP 与 PHP 5 中的 CIDR 掩码匹配?
无法使用上面给出的示例,因为 IPv6 地址的长度为 128 位,从而阻止了按位左移功能正常工作。你能想到别的办法吗?
编辑:将我自己的解决方案添加到答案列表中。
由于您无法将 IPv6 地址转换为整数,因此您应该操作位,如下所示:
$ip='21DA:00D3:0000:2F3B:02AC:00FF:FE28:9C5A';
$cidrnet='21DA:00D3:0000:2F3B::/64';
// converts inet_pton output to string with bits
function inet_to_bits($inet)
{
$splitted = str_split($inet);
$binaryip = '';
foreach ($splitted as $char) {
$binaryip .= str_pad(decbin(ord($char)), 8, '0', STR_PAD_LEFT);
}
return $binaryip;
}
$ip = inet_pton($ip);
$binaryip=inet_to_bits($ip);
list($net,$maskbits)=explode('/',$cidrnet);
$net=inet_pton($net);
$binarynet=inet_to_bits($net);
$ip_net_bits=substr($binaryip,0,$maskbits);
$net_bits =substr($binarynet,0,$maskbits);
if($ip_net_bits!==$net_bits) echo 'Not in subnet';
else echo 'In subnet';
此外,如果您使用一些数据库来存储 IP,它可能已经具备了比较它们的所有功能。例如,Postgres 具有 inet 类型,并且可以确定 IP 是否包含在子网中,如下所示:
SELECT
'21DA:00D3:0000:2F3B:02AC:00FF:FE28:9C5A'::inet <<
'21DA:00D3:0000:2F3B::/64'::inet;
你也可以使用symfony/http-foundation包中的IpUtils
类:
IpUtils::checkIp6('2a01:8760:2:3001::1', '2a01:8760:2:3001::1/64')
这将检查 IPv6 有效性和范围匹配。false
如果不是这种情况,将返回。
我使用以下代码创建了自己的解决方案:
function iPv6MaskToByteArray($subnetMask) {
$addr = str_repeat("f", $subnetMask / 4);
switch ($subnetMask % 4) {
case 0:
break;
case 1:
$addr .= "8";
break;
case 2:
$addr .= "c";
break;
case 3:
$addr .= "e";
break;
}
$addr = str_pad($addr, 32, '0');
$addr = pack("H*" , $addr);
return $addr;
}
function iPv6CidrMatch($address, $subnetAddress, $subnetMask) {
$binMask = iPv6MaskToByteArray($subnetMask);
return ($address & $binMask) == $subnetAddress;
}
注意$address 和$subnetAddress 是通过inet_pton 运行字符串地址获得的。调用函数如下:
$subnet = inet_pton("2001:06b8::");
$mask = 32;
$addr = inet_pton("2001:06b8:0000:0000:0000:0000:1428:07ab");
$match = iPv6CidrMatch($addr, $subnet, $mask); // TRUE
下面是一个示例,它通过根据单个 IP 或 CIDR(IPv4 和 IPv6)列表检查 IP 地址:
https://gist.github.com/lyquix-owner/2620da22d927c99d57555530aab3279b
<?php
// IP to check
$ip_check = $_SERVER['REMOTE_ADDR'];
// Array of allowed IPs and subnets, both IPv4 and IPv6
$ips_allowed = array(
'192.30.252.0/22',
'2620:112:3000::/44',
'192.168.16.104'
);
// Flag for IP match allowed list
$allow = false;
foreach($ips_allowed as $ip_allow) {
// If IP has / means CIDR notation
if(strpos($ip_allow, '/') === false) {
// Check Single IP
if(inet_pton($ip_check) == inet_pton($ip_allow)) {
$allow = true;
break;
}
}
else {
// Check IP range
list($subnet, $bits) = explode('/', $ip_allow);
// Convert subnet to binary string of $bits length
$subnet = unpack('H*', inet_pton($subnet)); // Subnet in Hex
foreach($subnet as $i => $h) $subnet[$i] = base_convert($h, 16, 2); // Array of Binary
$subnet = substr(implode('', $subnet), 0, $bits); // Subnet in Binary, only network bits
// Convert remote IP to binary string of $bits length
$ip = unpack('H*', inet_pton($ip_check)); // IP in Hex
foreach($ip as $i => $h) $ip[$i] = base_convert($h, 16, 2); // Array of Binary
$ip = substr(implode('', $ip), 0, $bits); // IP in Binary, only network bits
// Check network bits match
if($subnet == $ip) {
$allow = true;
break;
}
}
}
if(!$allow) {
die('IP not allowed');
}
PHP 可以对字符串进行按位运算!
<?php
/**
* Does the given IP match the CIDR prefix?
*/
function matchIp(string $ip, string $cidr): bool
{
// Get mask bits
list($net, $maskBits) = explode('/', $cidr);
// Size
$size = (strpos($ip, ':') === false) ? 4 : 16;
// Convert to binary
$ip = inet_pton($ip);
$net = inet_pton($net);
if (!$ip || !$net) {
throw new InvalidArgumentException('Invalid IP address');
}
// Build mask
$solid = floor($maskBits / 8);
$solidBits = $solid * 8;
$mask = str_repeat(chr(255), $solid);
for ($i = $solidBits; $i < $maskBits; $i += 8) {
$bits = max(0, min(8, $maskBits - $i));
$mask .= chr((pow(2, $bits) - 1) << (8 - $bits));
}
$mask = str_pad($mask, $size, chr(0));
// Compare the mask
return ($ip & $mask) === ($net & $mask);
}
如果你的掩码总是能被四整除(这在 ipv6 中很常见)。您可以使用:
function checkIPv6WithinRange($ipv6, $range) {
list ($net, $mask) = preg_split("/\//", $range);
if ($mask % 4)
throw new NotImplementedException("Only masks divisible by 4 are supported");
$stripChars = (128-$mask)/4;
$hexNet = bin2hex(inet_pton($net));
$reducedNet = substr($hexNet, 0, 0 - $stripChars);
$hexIp = bin2hex(inet_pton($ipv6));
$reducedIp = substr($hexIp, 0, 0 - $stripChars);
return $reducedIp === $reducedNet;
}
基本上是http://www.phpclasses.org/browse/file/70429.html
要使用它,只需调用
$cidr = new CIDR();
$cidr->match($ipv6, $ipv6_in_cidr);
结果是“好”。