Php将ipv6转换为数字

时间:2013-08-16 15:18:34

标签: php

在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

7 个答案:

答案 0 :(得分:9)

使用:

$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

功能:

启用 GMP BCMATH 扩展程序。

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

答案 1 :(得分:8)

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

  1. IPv6支持
  2. GMP extension (--with-gmp)
  3. 有了两个先决条件,转换就像:

    一样简单
    $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));
    }
    

答案 2 :(得分:7)

$ 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);
    }
    return base_convert(ltrim($binNum, '0'), 2, 10);
}

答案 3 :(得分:1)

试试这个

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/

答案 4 :(得分:1)

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

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

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

我会看到我能做什么,我会编辑这个答案。但是我同时也愿意为其他愿意提供帮助的人发布此信息。

答案 5 :(得分:1)

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

这将:

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

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

十六进制为十进制

    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地址的需求。

答案 6 :(得分:0)

boen_robot的答案版本,它避免了溢出问题,并使用BC Math(如果可用)。

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

示例:

openssl

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