8wDlpd.png
8wDFp9.png
8wDEOx.png
8wDMfH.png
8wDKte.png

这个 PHP 函数是否正确检查 IPV6 是否在给定的前缀中?

Muhammed Sahad 2月前

16 0

我需要检查访问者的 IPv6 是否在给定的前缀中(我有一些列入白名单的 IPv6 前缀)。我使用 StackOverflow 中的两个不同函数调整了以下函数(一个是将 IPv6

我需要检查访问者 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 运算符根据其掩码修改其值。

为了演示该问题,请考虑以下示例输入: 2001:db8:abc:1403::/54

预期结果:

First: 2001:db8:abc:1400:: Actual result:

First: 2001:db8:abc:1403::

[...] 修复的完整代码 [...]

当我尝试应用上面“更正”中的代码时,它无法匹配 2a03:2880:f800:4:: 此前缀内的 IP:。也许 2a03:2880:f800::/48 我错误地应用了修复,但是,最后提到的这个更正是否适用于我上面的最终代码?

帖子版权声明 1、本帖标题:这个 PHP 函数是否正确检查 IPV6 是否在给定的前缀中?
    本站网址:http://xjnalaquan.com/
2、本网站的资源部分来源于网络,如有侵权,请联系站长进行删除处理。
3、会员发帖仅代表会员个人观点,并不代表本站赞同其观点和对其真实性负责。
4、本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
5、站长邮箱:yeweds@126.com 除非注明,本帖由Muhammed Sahad在本站《php》版块原创发布, 转载请注明出处!
最新回复 (0)
  • 您的整个过程都是倒退的。 无需 计算前缀的第一个和最后一个地址,甚至无需计算“灵活位”,即可进行检查 - 您需要做的就是比较给定地址和前缀的初始 X 位(即“灵活”位)。

    (二进制到十六进制的转换通常也是没有意义的 - 如果您需要对每个半字节进行 hex2bin() 操作,那么您最好首先保留整个字符串二进制,因为无论您是在 4 位还是 8 位单元上工作,甚至在 32 位单元上工作都没有区别。)

    记住诸如此类的老式网络掩码的 255.255.128.0 工作原理很有用 – 术语“掩码”意味着它将直接按位与地址进行“与”运算,然后比较结果;也就是说 (address & mask) == net 。这不会因 CIDR 前缀而改变;唯一改变的是写下相同网络掩码的语法。(实际上,一些系统在内部将 \'/48\' 预扩展为完整掩码,例如 \'ffff:ffff:ffff::\',以便更快地进行比较。)

    例子:

    function ip_cidr($host, $mask) {
        list ($net, $len) = explode("/", $mask, 2);
    
        $host = inet_pton($host);
        $net = inet_pton($net);
        if ($host === false || $net === false || (strlen($len) && !is_numeric($len)))
            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 \InvalidArgumentException();
    
        $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);
            $len -= 8;
            /* if less than 8 bits left to check, generate an 'AND' mask for them */
            $bmask = (0xFF00 >> $bits) & 0xFF;
    
            //printf("host[%d] = %02x, net[%d] = %02x, bits = %d, bmask = %02x\n",
            //       $i, $host[$i], $i, $net[$i], $bits, $bmask);
    
            if (($host[$i] & $bmask) !== ($net[$i] & $bmask))
                return false;
        }
        return true;
    }
    

    如您所见,大多数功能只是预检查,以按位与比较作为实际检查( if (($host[$i] ^ $net[$i]) & $bmask) 如果您希望少一个操作,可以进行此操作)。

    相同的功能适用于 IPv4 和 IPv6。


    奖励:范围开始-结束

    如果出于其他目的需要,可以使用与(后者是按位非运算)来 net & mask 计算 net | ~mask 前缀的第一个或最后一个地址

    几乎 与您找到的原始“范围计算器”中的过程相同,只是向前循环而不是向后循环(在我看来,最初的“无用”迭代非常值得降低复杂性),以及使用按位运算而不是 2 位-1来计算 AND/OR 掩码:

    function ip_range($mask) {
        @list ($net, $len) = explode("/", $mask, 2);
        $net = inet_pton($net);
        if ($net === false || !is_numeric("0$len"))
            throw new \InvalidArgumentException();
    
        $nbits = strlen($net) * 8;
        $len = strlen($len) ? intval($len) : $nbits;
        if ($len < 0 || $len > $nbits)
            throw new \InvalidArgumentException();
    
        $net = unpack("C*", $net);
        $first = [];
        $last = [];
        for ($i = 1; $i <= count($net); $i++) {
            $bits = min($len, 8);
            $len = max($len - 8, 0);
            $bmask = (0xFF00 >> $bits) & 0xFF;
    
            $first[$i] = $net[$i] & $bmask;
            $last[$i] = $net[$i] | ~$bmask & 0xFF;
        }
    
        $first = inet_ntop(pack("C*", ...$first));
        $last = inet_ntop(pack("C*", ...$last));
        return [$first, $last];
    }
    

    (我想让它 $len -= $bits 在两个函数中同样有效,但是由于某种原因,我睡眠不足的大脑拒绝相信它。我确信是正确的,但 max() 似乎仍然更具可读性。)


    关于原始代码

    当我尝试应用上述“修正”中的代码时,它无法匹配此前缀 2a03:2880:f800:4:: 内的 IP:2a03:2880:f800::/48。也许我错误地应用了修复,但是,最后提到的修正是否适用于我上面的最终代码?

    它适用于前缀设置了“主机”位的输入,例如 2a03:2880:f800::/32 – 这是表达网络接口配置(主机地址 + 网络掩码)的有效方式,但严格来说作为前缀并不有效。(此示例中的前缀为 2a03:2880::/32。)

    因此,如果您可以确保您的“受信任前缀”列表已经是规范形式(没有设置任何“主机”位),那么该函数 可以 假定前缀等于该前缀的第一个地址,并且实际上不需要应用更正。

    但是,无论哪种方式,该函数都应该返回相同的结果(假设输入规范),因此对于你的情况,我认为你一定是错误地应用了修正。你没有展示更新后的代码,但 Allen Ellis 的原始帖子似乎是正确的——基本上整个“最后地址”的计算都必须重复并反转。

    (在我的示例 ip_range() 函数中, $first[i] = $net[i] & $bmask 执行的是相同的 AND 运算。)

    如果您想尝试调试代码,我 至少 会将其分成一个单独的“计算第一个-最后一个”函数,以便您可以单独测试它(确保它始终计算正确的第一个地址等),并且只有在您使其正常工作后,才用单独的“检查是否在 ab 范围内”函数包裹它。

    但我不会花任何时间来尝试改进当前代码。正如开头提到的,从 的角度实现前缀检查 first <= addr <= last 确实有点落后,还有一种更直接的方法可以做到这一点,这种方法本质上不会对非规范前缀产生任何问题。

返回
作者最近主题: