inet_pton()是否因为某些IPv6地址而被破坏了#34;看起来像#34; IPv4地址?

时间:2015-03-06 18:54:27

标签: php ipv6

我使用PHP版本5.2.17,我发现以下内容按预期工作:

$x = inet_pton('::F');
$y = inet_ntop($x);
print "::F -> $y\n";

Output: ::F -> ::f

但以下情况并非如此:

$a = inet_pton('::FEEF:1886');
$b = inet_ntop($a);
print "::FEEF:1886 -> $b\n";

Output: ::FEEF:1886 -> ::254.239.24.134

我原本期望第二个代码片段产生这个输出:

::FEEF:1886 -> ::feef:1886

IPv6地址:: FEEF:1886是什么让PHP认为它真的是一个IPv4地址? inet_ntop / inet_pton转换与" high"中的其他地址0正确地工作。 96位(例如:: F)。

编辑: 我的第一个想法是,这可能是我的PHP版本中的错误,但是使用this online PHP sandbox我看到了相同的行为适用于5.6.2版本的PHP版本。所以要么这是故意的(在这种情况下,我非常想知道这种行为的原因),或者是现代版本的PHP中存在的错误。

ADDENDUM: 我在2015年3月12日打开了PHP Bug 69232,因为inet_ntop()对于:: / 96中地址的行为明显不一致。

5 个答案:

答案 0 :(得分:3)

IPv6地址的文本表示允许每个IPv6地址的多个不同的有效表示。

这意味着IPv6地址的所有这些有效文本表示在通过inet_pton时都映射到相同的二进制128位字符串。

然而,当使用inet_ntop将二进制128位字符串转换为文本表示时,显然它只能输出表示该IP地址的许多有效字符串之一。它选择的那个被称为规范表示。

使用IPv4表示法写入IPv6地址的最后32位始终有效。但是,只使用了几类IPv6地址作为规范表示格式。

::/96已被弃用,但这只是意味着这些地址不再被使用,它不会影响inet_ptoninet_ntop对待它们的方式。

::ffff:0.0.0.0/96是另一个前缀,它在规范表示中使用IPv4表示法。该前缀用于套接字API中的IPv4兼容性,但它们永远不会在线路上发送,因为它们适用于线路上的流量为IPv4的情况。

答案 1 :(得分:1)

您正在查看的是IPv4地址(错误地)表示为IPv6地址。这种做法于2006年由RFC 4291正式弃用:

  

“IPv4兼容的IPv6地址”现已弃用,因为     当前的IPv6过渡机制不再使用这些地址。     不需要新的或更新的实现来支持这一点     地址类型。

答案 2 :(得分:1)

试试这个:

function _inet_ntop($ip) {

  if (strlen($ip) == 4) { // For IPv4
    list(, $ip) = unpack('N', $ip);
    $ip = long2ip($ip);
  }
  elseif(strlen($ip) == 16) { // For IPv6
    $ip = bin2hex($ip);
    $ip = substr(chunk_split($ip, 4, ':'), 0, -1);
    $ip = explode(':', $ip);
    $res = '';

    foreach($ip as $index => $seg) {
      while ($seg {0} == '0')
        $seg = substr($seg, 1);

      if ($seg != '') {
        $res .= $seg;
        if ($index < count($ip) - 1)
          $res .= $res == '' ? '' : ':';
      } else {
        if (strpos($res, '::') === false)
          $res .= ':';

      }
    }
    $ip = $res;
  }

  return $ip;
}

您可以调用此函数而不是inet_ntop

$a = inet_pton('::FEEF:1886');
$b = _inet_ntop($a);
print "::FEEF:1886 -> $b\n";
// Output => ::FEEF:1886 -> ::feef:1886

$x = inet_pton('::F');
$y = _inet_ntop($x);
print "::F -> $y\n";
// Output => ::F -> ::f

答案 3 :(得分:0)

总结我到目前为止在答案和答案中所做的事情。注释:

  • IPv6地址具有规范格式,即inet_ntop()返回的格式。
  • 不推荐使用:: / 96地址范围,但:: ffff / 80不是。
  • 虽然通过inet_ntop()将所有 :: / 96地址呈现为:: / IPv4-address是有意义的,但似乎inet_ntop()呈现:: / 112地址和& #34;更高&#34;在:: / 96 as :: / IPv4-dotted-quad(例如:: 254.239.24.134)中渲染:: / 96地址&#34;降低&#34;比:: / 112和#34;正常&#34; IPv6地址。
  • 如果你想让inet_ntop()以相同的方式呈现所有IPv6地址(即8个十六进制字,并使用通常的零压缩规则),那么你需要编写自己的方法来实现这一点。

我自己的解决方法是通过将任何IPv4点分四边形重写为六进制来扩展inet_ntop()(我将逻辑分解为多种方法,以便我更容易跟踪我正在做的事情):

function _inet_ntop($addr) {
    return fix_ipv4_compatible_ipv6(inet_ntop($addr));
}

/**
 * If $str looks like ::/IPv4-dotted-quad then rewrite it as
 * a "pure" IPv6 address, otherwise return it unchanged.
 */
function fix_ipv4_compatible_ipv6($str) {
    if (
        ($str[0] == ':') &&
        ($str[1] == ':') &&
        preg_match('/^::(\S+\.\S+)$/', $str, $match)
    ) {
        $chunks = explode('.', $match[1]);
        return self::ipv4_zones_to_ipv6(
            $chunks[0],
            $chunks[1],
            $chunks[2],
            $chunks[3]
        );
    } else {
        return $str;
    }
}

/**
 * Return a "pure" IPv6 address printable string representation
 * of the ::/96 address indicated by the 4 8-bit "zones" of an
 * IPv4 address (e.g. (254, 239, 24, 134) -> ::feef:1886).
 */
function ipv4_zones_to_ipv6($q1, $q2, $q3, $q4) {
    if ($q1 == 0) {
        if ($q2 == 0) {
            if ($q3 == 0) {
                if ($q4 == 0) {
                    return '::0';
                } else {
                    return '::' . self::inflate_hexbit_pair($q4);
                }
            } else {
                return '::' . self::inflate_hex_word($q3, $q4);
            }
        } else {
            return '::' . self::inflate_hexbit_pair($q2) . ':' . self::inflate_hex_word($q3, $q4);
        }
    } else {
        return '::' . self::inflate_hex_word($q1, $q2) . ':' . self::inflate_hex_word($q3, $q4);
    }
}

/**
 * Convert two 8-bit IPv4 "zones" into a single 16-bit hexword,
 * stripping leading 0s as needed, e.g.:
 * (254, 239) -> feef
 * (0,1) -> 1
 */
function inflate_hex_word($hb1, $hb2) {
    $w = self::inflate_hexbit_pair($hb1) . self::inflate_hexbit_pair($hb2);
    return ltrim($w, '0');
}

/**
 * Convert one 8-bit IPv4 "zone" into two hexadecimal digits,
 * (hexits) padding with a leading zero if necessary, e.g.:
 * 254 -> fe
 * 2 -> 02
 */
function inflate_hexbit_pair($hb) {
    return str_pad(dechex($hb), 2, '0', STR_PAD_LEFT);
}

虽然可以说很多不如JC Sama提出的_inet_ntop()函数优雅,但它比我的(基本上是随机的)测试用例运行速度快25%。

答案 4 :(得分:0)

kasperd,diskwuff和JC Sama提供的答案提供了有用的信息和解决方法,这些信息可能对其他SO读者有用,因此我对它们进行了全面的投票。但他们没有直接解决我的原始问题,所以我添加了这个答案:

PHP函数inet_pton()的行为是正确的。问题是inet_ntop()不会始终如一地处理:: / 96中的IPv6地址。 This is a bug in PHP