17

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

如何将 ipv6 压缩为 PHP 中的数字?

我尝试了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
4

7 回答 7

12

利用:

$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

功能:

启用GMPBMATH扩展。

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

于 2013-10-21T14:38:01.493 回答
12

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

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

有了这两个先决条件,转换就很简单:

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

echo $int; // 340282366920938463463374607431768211455

返回的二进制数字打包in_addr表示inet_pton已经是一个整数,可以直接导入 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));
}
于 2016-05-16T09:17:27.597 回答
9

$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);
}
于 2013-08-16T15:38:54.503 回答
2

尝试这个

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/

于 2013-08-16T15:23:35.463 回答
2

这是将十六进制转换为十进制和将十进制转换为十六进制的两个函数。这仅适用于 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 地址的需求。

于 2019-01-15T16:54:40.863 回答
1

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

这个 geoIP 数据库提供的 DB 布局太慢了,无法在开始和结束时应用普通的 BETWEEN,即使存储是您可以获得的最经济的存储。

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

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

于 2013-08-16T20:32:32.033 回答
0

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”作为字符串返回,这是正确的答案。

于 2019-07-19T18:39:52.213 回答