我需要检查访客 IPv6 是否在给定前缀中(我有一些列入白名单的 ipv6 前缀)。
我使用 StackOverflow 中的两个不同的函数调整了下面的函数(一个是 将 ipv6 前缀转换为第一个和最后一个地址,另一个答案是测试给定的 IP 是否在给定的第一个 + 最后一个 ip 范围内),但是看看代码里有很多我不熟悉的功能,加上ipv6让我很困惑。
有人可以告诉我这段代码是否正确吗?根据我的测试,它验证了我发送给它的 IP:
<?php
if(!function_exists('is_ipv6_in_prefix')){
function is_ipv6_in_prefix($ipv6_ip_to_check, $ipv6_prefix_to_check){
$ipv6_ip_to_check = inet_pton($ipv6_ip_to_check);
// Split in address and prefix length
list($firstaddrstr, $prefixlen) = explode('/', $ipv6_prefix_to_check);
// Parse the address into a binary string
$firstaddrbin = inet_pton($firstaddrstr);
// Convert the binary string to a string with hexadecimal characters
if(function_exists('bin2hex')){
$firstaddrhex = bin2hex($firstaddrbin);
} else {
// older php versions can use pack/unpack
$firstaddrhex = reset(unpack('H*', $firstaddrbin));
}
// Overwriting first address string to make sure notation is optimal
$firstaddrstr = inet_ntop($firstaddrbin);
// Calculate the number of 'flexible' bits
$flexbits = 128 - $prefixlen;
// Build the hexadecimal string of the last address
$lastaddrhex = $firstaddrhex;
// We start at the end of the string (which is always 32 characters long)
$pos = 31;
while ($flexbits > 0) {
// Get the character at this position
$orig = substr($lastaddrhex, $pos, 1);
// Convert it to an integer
$origval = hexdec($orig);
// OR it with (2^flexbits)-1, with flexbits limited to 4 at a time
$newval = $origval | (pow(2, min(4, $flexbits)) - 1);
// Convert it back to a hexadecimal character
$new = dechex($newval);
// And put that character back in the string
$lastaddrhex = substr_replace($lastaddrhex, $new, $pos, 1);
// We processed one nibble, move to previous position
$flexbits -= 4;
$pos -= 1;
}
// Convert the hexadecimal string to a binary string
if(function_exists('hex2bin')){
$lastaddrbin = hex2bin($lastaddrhex);
} else {
// older PHP versions can use pack/unpack
$lastaddrbin = pack('H*', $lastaddrhex);
}
// And create an IPv6 address from the binary string
$lastaddrstr = inet_ntop($lastaddrbin);
// print string values for user
// echo "\nPrefix: $ipv6_prefix_to_check";
// echo "\nFirst: $firstaddrstr";
// echo "\nLast: $lastaddrstr";
if ((strlen($ipv6_ip_to_check) == strlen($firstaddrbin)) && ($ipv6_ip_to_check >= $firstaddrbin && $ipv6_ip_to_check <= $lastaddrbin)) {
// In range
return true;
} else {
// Not in range
return false;
}
}
}
if(is_ipv6_in_prefix('2a03:2880:f800:4::', '2a03:2880:f800::/48')){
echo "\nTRUE";
} else {
echo "\nFALSE";
}
?>
我不确定,因为另一个答案带有更正并说:
这是对已接受答案的修复,该答案错误地假设“第一个地址”应与输入的字符串相同。相反,它需要通过 AND 运算符针对其掩码修改其值。当我尝试应用上面“更正”中的代码时,它无法匹配此前缀内的 IP为了演示该问题,请考虑以下示例输入:
预期结果:
2001:db8:abc:1403::/54
First: 2001:db8:abc:1400:: Actual result:
[...]修复的完整代码[...]
First: 2001:db8:abc:1403::
2a03:2880:f800:4::
:
2a03:2880:f800::/48
。 也许我错误地应用了修复程序,但是最后提到的这个修正是否适用于我上面的最终代码?
无需计算前缀的第一个和最后一个地址,甚至不需要“灵活位”计算 - 您需要做的就是比较给定地址和前缀的初始 X 位(二进制形式) .
二进制到十六进制的转换通常也是毫无意义的 - 如果您需要 hex2bin() 每个半字节对其进行操作,那么您最好将整个字符串二进制放在第一位,因为这对无论您使用的是 4 位还是 8 位单元(或 32 位单元),请遵循以下步骤。示例:
function ip_cidr($host, $mask) {
list ($net, $len) = explode("/", $mask, 2);
$host = inet_pton($host);
$net = inet_pton($net);
if ($host === false || $net === false)
throw new \InvalidArgumentException();
elseif (strlen($host) !== strlen($net))
return false; /* mismatching address families not an error */
$nbits = strlen($host) * 8;
$len = strlen($len) ? intval($len) : $nbits;
if ($len < 0 || $len > $nbits)
throw new \DomainException();
elseif ($len == 0)
return true; /* an /0 always matches */
elseif ($len == $nbits)
return $host === $net;
$host = unpack("C*", $host);
$net = unpack("C*", $net);
/* loop over each byte and compare them */
for ($i = 1; $i <= count($net) && $len > 0; $i++) {
$bits = min($len, 8);
/* if we have less than 8 bits left to check, generate an 'AND' mask for them */
$bmask = (0xFF00 >> $bits) & 0xFF;
$len -= 8;
//printf("pos = %d, bits = %d, bmask = %02x, host[%d] = %02x, net[%d] = %02x\n",
// $i, $bits, $bmask, $i, $host[$i], $i, $net[$i]);
if (($host[$i] & $bmask) !== ($net[$i] & $bmask))
return false;
}
return true;
}