【问题标题】:Php convert ipv6 to numberphp将ipv6转换为数字
【发布时间】:2021-06-03 18:26:11
【问题描述】:

在 IPv4 中我们可以使用ip2long 将其转换为数字,

PHP中如何将ipv6压缩成数字?

我尝试了inet_pton,但它不起作用。

$ip_1='2001:0db8:85a3:0000:0000:8a2e:0370:7334'; 
$ip_2='2001:11ff:ffff:f';//Compressed
echo inet_pton($ip_1); 
//OUTPUT  ИЃ.ps4
echo inet_pton($ip_2);
//OUTPUT Warning: inet_pton(): Unrecognized address 2001:11ff:ffff:f

【问题讨论】:

  • 你得到什么输出,有什么错误吗?
  • PHP 有 128 位的数据类型吗?
  • 第一个我得到奇怪的文本第二个压缩我得到错误
  • $ip_2 我认为不是有效的 IPv6。尝试这样的事情:2001:db8:85a3::8a2e:370:7334
  • ip2 肯定是无效的。它没有足够的位,也没有:: 零分组来显示它可以填充到 128 位的位置。

标签: php


【解决方案1】:

用途:

$ip  = 'fe80:0:0:0:202:b3ff:fe1e:8329';
$dec = ip2long_v6($ip);
$ip2 = long2ip_v6($dec);

// $ip  = fe80:0:0:0:202:b3ff:fe1e:8329
// $dec = 338288524927261089654163772891438416681
// $ip2 = fe80::202:b3ff:fe1e:8329

功能:

启用 GMPBCMATH 扩展。

function ip2long_v6($ip) {
    $ip_n = inet_pton($ip);
    $bin = '';
    for ($bit = strlen($ip_n) - 1; $bit >= 0; $bit--) {
        $bin = sprintf('%08b', ord($ip_n[$bit])) . $bin;
    }

    if (function_exists('gmp_init')) {
        return gmp_strval(gmp_init($bin, 2), 10);
    } elseif (function_exists('bcadd')) {
        $dec = '0';
        for ($i = 0; $i < strlen($bin); $i++) {
            $dec = bcmul($dec, '2', 0);
            $dec = bcadd($dec, $bin[$i], 0);
        }
        return $dec;
    } else {
        trigger_error('GMP or BCMATH extension not installed!', E_USER_ERROR);
    }
}

function long2ip_v6($dec) {
    if (function_exists('gmp_init')) {
        $bin = gmp_strval(gmp_init($dec, 10), 2);
    } elseif (function_exists('bcadd')) {
        $bin = '';
        do {
            $bin = bcmod($dec, '2') . $bin;
            $dec = bcdiv($dec, '2', 0);
        } while (bccomp($dec, '0'));
    } else {
        trigger_error('GMP or BCMATH extension not installed!', E_USER_ERROR);
    }

    $bin = str_pad($bin, 128, '0', STR_PAD_LEFT);
    $ip = array();
    for ($bit = 0; $bit <= 7; $bit++) {
        $bin_part = substr($bin, $bit * 16, 16);
        $ip[] = dechex(bindec($bin_part));
    }
    $ip = implode(':', $ip);
    return inet_ntop(inet_pton($ip));
}

demo

【讨论】:

    【解决方案2】:

    请注意,对于大 IP 地址,所有答案都将导致不正确的结果,或者正在经历一个非常复杂的过程来接收实际数字。从 IPv6 地址中检索实际的整数值需要两件事:

    1. IPv6 支持
    2. GMP extension (--with-gmp)

    在满足这两个先决条件的情况下,转换非常简单:

    $ip = 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff';
    $int = gmp_import(inet_pton($ip));
    
    echo $int; // 340282366920938463463374607431768211455
    

    inet_pton 返回的二进制数字压缩in_addr 表示已经是一个整数,可以直接导入 GMP,如上图所示。不需要特殊的转换或任何东西。

    注意,反过来也同样简单:

    $int = '340282366920938463463374607431768211455';
    $ip = inet_ntop(str_pad(gmp_export($int), 16, "\0", STR_PAD_LEFT));
    
    echo $ip; // ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
    

    因此构建两个所需的功能非常简单:

    function ipv6_to_integer($ip) {
        return (string) gmp_import(inet_pton($ip));
    }
    
    function ipv6_from_integer($integer) {
        return inet_ntop(str_pad(gmp_export($integer), 16, "\0", STR_PAD_LEFT));
    }
    

    【讨论】:

      【解决方案3】:

      $ip_2 不是有效的 IPv6 地址。您需要在其中某处使用“::”,以指示零遗漏点。

      如果你有它作为其中之一

      $ip_2='2001::11ff:ffff:f';
      $ip_2='2001:11ff::ffff:f';
      $ip_2='2001:11ff:ffff::f';
      

      那么 inet_pton() 应该可以正常工作。

      正如已经暗示的那样,PHP 没有 128 整数类型,所以你能得到的最好的结果是 二进制字符串 inet_pton() 给你的数字字符串......是的,那就是它是什么,这就是为什么它看起来很奇怪。如果您查看这些字符串的位,您会发现它们正是您所期望的。

      以下是如何将二进制字符串扩展为数字字符串(str_pad() 最初缺少参数“0”):

      /**
       * @param string $ip A human readable IPv4 or IPv6 address.
       * @return string Decimal number, written out as a string due to limits on the size of int and float.
       */
      function ipv6_numeric($ip) {
          $binNum = '';
          foreach (unpack('C*', inet_pton($ip)) as $byte) {
              $binNum .= str_pad(decbin($byte), 8, "0", STR_PAD_LEFT);
          }
          // $binNum is now a human readable string, but in binary.
          // If you have the gmp PHP extension, you can convert it to decimal
          return gmp_strval(gmp_init(ltrim($binNum, '0'), 2), 10);
      }
      

      【讨论】:

      • 那么我应该在数据库中存储什么?我尝试从 db 中查找 ip 位置,类似于 INET_ATON("123.243.51.83") BETWEEN INET_ATON(ip_start) AND INET_ATON(ip_end)
      • @Ben 好吧,这取决于。您可以只存储来自 inet_pton() 的二进制字符串。只有当您需要稍后对地址进行一些“数学运算”时,数字字符串才真正有用。如果您只是规范 IP 符号,比较 inet_pton() 的两个符号的输出就足够了。
      • @Ben 哦,所以你想检查范围......嗯,这是一种“数学”,所以是的。您可能希望使用上述函数,然后将结果传递给 GMP 函数之类的函数来进行数学运算。
      • @boen_robot:函数ipv6_numeric 无法正常工作。对于2001:11ff:ffff::f,它输出无效的42540853245347496466006288282062604240,应该是42540853245347499564798372846648688655,如here
      • 这是因为base_convert 存在溢出问题,PHP 手册中也有一个巨大的红色框警告这一点。看我的回答。
      【解决方案4】:

      试试这个

      function inet6_to_int64($addr)
      {
          /* Expand the address if necessary */
          if (strlen($addr) != 39) {
              $addr = inet6_expand($addr);
              if ($addr == false) return false;
          } // if
          $addr = str_replace(':', '', $addr);
          $p1 = '0x' . substr($addr, 0, 16);
          $p2 = '0x' . substr($addr, 16);
          $p1 = gmp_init($p1);
          $p2 = gmp_init($p2);
          $result = array(gmp_strval($p1), gmp_strval($p2));
          return $result;
      } // inet6_to_int64()
      

      更多功能或详情请访问 http://www.soucy.org/project/inet6/

      【讨论】:

      • 检查 GMP 支持是否启用
      【解决方案5】:

      这里有两个函数将十六进制转换为十进制和十进制转换为十六进制。这仅适用于 IPv6 十六进制和 IPv6 整数表示(因此,对 IPv4 数字使用 ip2long() 和 long2ip())。从理论上讲,可以将 IPv4 点分符号数字转换为十六进制值并使用这些值,但这可能是矫枉过正。

      这将:

      • 将所有完整的 IPv6 数字转换为字符串化的 long int(最多 39 个字符,如果标志设置为 true,则左填充以便排序。
      • 将字符串化的“long”转换回十六进制 IPv6 表示形式,根据需要将填充保留为完整的 32 位十六进制字符串。如果适当的标志设置为真,则可以选择从右到左放置冒号。

      可以修改这些以处理几乎任何长度的十六进制值或几乎任何长度的整数,并且可以相应地调整冒号和填充的位置。

      十六进制转十进制

          function bchexdec($hex,$padl)
          // Input: A hexadecimal number as a String.
          // Output: The equivalent decimal number as a String.
          // - if padl==true then pad left to fill 39 characters for string sort
      
          {
              if (strlen($hex) != 39) 
              {
                  $hex = inet6_expand($hex);
                  if ($hex == false) return false;
              }
      
              $hex=str_replace(":","",$hex);
              $dec = 0;
              $len = strlen($hex);
              for ($i = 1; $i <= $len; $i++) 
              {
                  $dec = bcadd($dec, bcmul(strval(hexdec($hex[$i - 1])), bcpow('16', strval($len - $i))));
              }
      
              if ($padl==true)
              {
                  $dec=str_pad($dec,39,"0",STR_PAD_LEFT);
              }
              return $dec;
      
          }
      

      十进制转十六进制

          function bcdechex($dec,$colon) 
          // Input: A decimal as a String.
          // Output: The equivalent hex value.
          // - if $colon==true then add colons.   
          {
              $hex = '';
      
              // RFC 5952, A Recommendation for IPv6 Address Text Representation, Section 4.3 Lowercase specifies letters should be lowercase. Though that generally doesn't matter, use lowercase
              //   to conform with the RFC for those rare systems that do so. 
              do 
              {    
                  $last = bcmod($dec, 16);
                  $hex = dechex($last).$hex;
                  $dec = bcdiv(bcsub($dec, $last), 16);
              } while($dec>0);
              $hex=str_pad($hex,32,"0",STR_PAD_LEFT);
              // Add colons if $colon==true
              if ($colon==true)
              {
                  $hex=strrev($hex);
                  $chunks=str_split($hex,4);
                  $hex=implode(":", $chunks);
                  $hex=strrev($hex);
              }
      
              return $hex;
          }
      

      这是基于在很多地方找到的想法和示例,以及我自己对易于分类和易于存储的 IPv6 地址的需求。

      【讨论】:

      • 如果您在地址中有任何跳过的零或::,您将得到意外的错误结果,将其转换为数字字符串。 str_replace(":","",$hex) 只会降低最重要的地址值。确保您在上述功能中使用完整的 IPv6 地址。
      • @CarldiOrtus 是的,我在写它的第一个要点中注意到:“将所有 complete IPv6 数字转换为字符串化的 long int(最多 39 个字符,左侧如果标志设置为 true,则填充以进行排序。” - 也许我可以使用更好的措辞?
      • 是的,我可能只是在您的句子中漏掉了一个词,只是在尝试后才发现。也许您可以通过在项目符号上方的段落中添加相同的信息来改进您的答案?这个答案对于那些没有gmp 支持的不幸的人来说是件好事。要将 IPv6 地址扩展为完整,请查看用户 usman allam 答案提供的链接。
      【解决方案6】:

      好的,与 Ben Wong 聊天的一些启示...真正的问题是优化数据库中有关 geoIP 服务的查找。

      this geoIP database 提供的 DB 布局太慢,无法在开始和结束时应用简单的 BETWEEN,即使存储是您可以获得的最经济的存储。

      我在聊天中首先提出的建议是将 IP 地址分成 4 个整数,然后按顺序进行比较,但在第二个中,这可能还不够,因为您仍在搜索整个数据库,这已经结束了100 万行。我也有一个关于使用子网掩码进行匹配的想法,但鉴于某些范围不在大掩码内,再次这样做可能还不够。

      我会看看我能做什么,我会编辑这个答案。但与此同时,我会将其发布给任何愿意为此提供帮助的人。

      【讨论】:

        【解决方案7】:

        boen_robot 的答案版本,可避免溢出问题,如果可用,请使用 BC Math

        function ipv62numeric($ip)
        {
            $str = '';
            foreach (unpack('C*', inet_pton($ip)) as $byte) {
                $str .= str_pad(decbin($byte), 8, '0', STR_PAD_LEFT);
            }
            $str = ltrim($str, '0');
            if (function_exists('bcadd')) {
                $numeric = 0;
                for ($i = 0; $i < strlen($str); $i++) {
                    $right  = base_convert($str[$i], 2, 10);
                    $numeric = bcadd(bcmul($numeric, 2), $right);
                }
                $str = $numeric;
            } else {
                $str = base_convert($str, 2, 10);
            }
        
            return $str;
        }
        

        例子:

        echo ipv62numeric('2001:11ff:ffff::f');
        

        将“42540853245347499564798372846648688655”作为字符串返回,这是正确的答案。

        【讨论】:

          猜你喜欢
          • 2015-03-28
          • 2013-07-26
          • 2012-08-07
          • 2018-01-23
          • 2022-07-05
          • 1970-01-01
          • 2017-12-17
          • 2020-07-30
          • 2011-03-27
          相关资源
          最近更新 更多