Не работает ли inet_pton () для некоторых адресов IPv6, которые & quot; выглядят как & quot; IPv4-адреса?

Я использую 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.

1

Решение

Текстовое представление адресов 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.

3

Другие решения

То, на что вы обращаете внимание, — это адрес IPv4, представляемый (неправильно) как адрес IPv6. Эта практика была официально устарела в 2006 году RFC 4291:

«IPv4-совместимый адрес IPv6» теперь устарел, потому что
Текущие механизмы перехода IPv6 больше не используют эти адреса.
Новые или обновленные реализации не обязаны поддерживать это
тип адреса.

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
1

Подводя итог, что я до сих пор в ответах & Комментарии:

  • Адреса IPv6 имеют канонический формат, который возвращает inet_ntop ().
  • Диапазон адресов :: / 96 устарел, а :: ffff / 80 — нет.
  • Хотя это будет иметь смысл для все :: / 96 адресов, которые будут отображаться как :: / IPv4-адрес с помощью inet_ntop (), кажется, что inet_ntop () отображает :: / 112 адресов и «выше» в :: / 96 как :: / IPv4-dotted-quad ( например, :: 254.239.24.134) и отображает адреса :: / 96 «ниже», чем :: / 112, как «нормальные» адреса IPv6.
  • Если вы хотите, чтобы inet_ntop () отображала все адреса IPv6 одинаково (т.е. 8 шестнадцатеричных слов с обычными правилами нулевого сжатия), вам нужно написать собственный метод для достижения этой цели.

Мой собственный обходной путь — расширить 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% быстрее по сравнению с моими (в основном случайными) тестовыми примерами.

0

Ответы, предоставленные kasperd, diskwuff и JC Sama, содержат как полезную информацию, так и обходные пути, которые могут быть полезны для других читателей SO, поэтому я проголосовал за них всех. Но они не обращаются непосредственно к моему первоначальному вопросу, поэтому я добавляю этот ответ:

Поведение функции PHP inet_pton () является правильным. Проблема в том, что inet_ntop () не обрабатывает адрес IPv6 в :: / 96 последовательно. Это ошибка в PHP.

0
По вопросам рекламы [email protected]