Php конвертировать ipv6 в число

В 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

Ответ 1

$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(), которая дает вам... да, это что это такое, и почему это выглядит странно. Если вы посмотрите на биты этих строк, вы увидите, что они именно то, что вы ожидаете.

Здесь, как развернуть двоичную строку в числовую строку (аргумент "0" отсутствовал из str_pad() изначально):

/**
 * @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);
}

Ответ 2

Использование:

$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));
}

демонстрация

Ответ 3

Обратите внимание, что все ответы приведут к неверным результатам для больших 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));
}

Ответ 4

Попробуйте это

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/

Ответ 5

ОК, некоторые откровения из чата с Беном Вонгом... РЕАЛЬНАЯ проблема - это оптимизация поиска в базе данных о службе geoIP.

Макет БД, предлагаемый этой базой данных geoIP, слишком медленный, чтобы просто использовать значение "BETWEEN" в начале и в конце, хотя хранилище является наиболее экономичным, которое вы можете получить.

Предложение, которое я впервые изложил в чате, состояло в том, чтобы фрагментировать IP-адрес в 4 типах, которые сравниваются последовательно, но на втором, хотя этого может быть недостаточно, поскольку вы все еще просматриваете всю БД, которая закончилась 1 миллион строк. Я также имел представление о выполнении масок с масками подсети, но учитывая, что некоторые диапазоны не находятся в больших масках, сделать это может, опять же, быть недостаточно.

Я посмотрю, что я могу сделать, и я отредактирую этот ответ. Но я отправляю это тем временем для тех, кто хочет помочь в этом.

Ответ 6

Здесь две функции, которые преобразуют шестнадцатеричное в десятичное и десятичное в шестнадцатеричное. Это работает только с шестнадцатеричными и целочисленными представлениями IPv6 (поэтому используйте ip2long() и long2ip() для номеров IPv4). Теоретически, можно преобразовать пунктирную запись IPv4 в шестнадцатеричные значения и использовать их, но это, вероятно, было бы излишним.

Это будет:

  • Преобразуйте все полные числа IPv6 в строковое длинное целое (до 39 символов, оставляя отступ для сортировки, если для флага установлено значение true.
  • Преобразуйте строковый "long" обратно в шестнадцатеричное представление IPv6, оставляя отступы по мере необходимости, до полной 32-битной шестнадцатеричной строки. Двоеточие необязательно размещается справа налево, если для соответствующего флага установлено значение true.

Они могут быть изменены для обработки практически любого шестнадцатеричного значения длины или практически любого целого числа длины, и размещение двоеточий и отступов может быть соответствующим образом скорректировано.

HEX для десятичного

    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;

    }

ПОСЛЕДУЮЩИЙ для HEX

    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.

Ответ 7

Версия ответа от 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" в виде строки, что является правильным ответом.