Я использую 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 корректно работает с другими адресами, имеющими 0 в «старших» 96 битах (например, :: F).
РЕДАКТИРОВАТЬ: Сначала я подумал, что это может быть ошибка в моей версии PHP, но с использованием это онлайн песочница PHP Я вижу такое же поведение для версий PHP до 5.6.2. Так что это либо намеренно (в этом случае я бы очень хотел знать причину такого поведения), либо ошибка, которая сохраняется в современных версиях PHP.
ДОПОЛНЕНИЕ: я открыл PHP Bug 69232 12 марта 2015 г. из-за этого очевидного несоответствия в поведении inet_ntop () для адресов в :: / 96.
Текстовое представление адресов IPv6 допускает несколько разных действительных представлений каждого адреса IPv6.
Это означает, что все эти действительные текстовые представления IPv6 адресуют каждую карту в одну и ту же двоичную 128-битную строку при прохождении через inet_pton
,
Однако при преобразовании двоичной 128-битной строки в текстовое представление с помощью inet_ntop
очевидно, что он может выводить только одну из множества допустимых строк, представляющих этот IP-адрес. Тот, который он выбрал, называется каноническим представлением.
Всегда допустимо записывать последние 32 бита адреса IPv6 с использованием записи IPv4. Однако только несколько классов адресов IPv6 использовали этот формат в качестве своего канонического представления.
::/96
устарела, но это просто означает, что эти адреса больше не должны использоваться, это не влияет на то, как они обрабатываются inet_pton
а также inet_ntop
,
::ffff:0.0.0.0/96
это еще один префикс, который использует нотацию IPv4 в своем каноническом представлении. Этот префикс используется для совместимости IPv4 в API сокетов, но они никогда не отправляются по проводам, потому что они предназначены для ситуаций, когда трафик по проводам будет IPv4.
То, на что вы обращаете внимание, — это адрес IPv4, представляемый (неправильно) как адрес IPv6. Эта практика была официально устарела в 2006 году RFC 4291:
«IPv4-совместимый адрес IPv6» теперь устарел, потому что
Текущие механизмы перехода IPv6 больше не используют эти адреса.
Новые или обновленные реализации не обязаны поддерживать это
тип адреса.
Попробуйте это:
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
Подводя итог, что я до сих пор в ответах & Комментарии:
Мой собственный обходной путь — расширить inet_ntop (), переписав любые четырехугольники с точками IPv4 в виде шестнадцатеричных слов (и я разбил логику на несколько методов, чтобы мне было легче отслеживать, что я делал):
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);
}
Хотя возможно много менее элегантная, чем функция _inet_ntop (), предложенная JC Sama, она работает примерно на 25% быстрее по сравнению с моими (в основном случайными) тестовыми примерами.
Ответы, предоставленные kasperd, diskwuff и JC Sama, содержат как полезную информацию, так и обходные пути, которые могут быть полезны для других читателей SO, поэтому я проголосовал за них всех. Но они не обращаются непосредственно к моему первоначальному вопросу, поэтому я добавляю этот ответ:
Поведение функции PHP inet_pton () является правильным. Проблема в том, что inet_ntop () не обрабатывает адрес IPv6 в :: / 96 последовательно. Это ошибка в PHP.