Как сравнить IP-адреса в PHP как двоичные строки?

В настоящее время я работаю с адресами IPv4 и IPv6 в проекте на основе PHP, и мне нужно иметь возможность сравнивать два IP-адреса, чтобы определить, какой из них больше. Например, этот 192.168.1.9 больше, чем 192.168.1.1. Для этого я преобразовал IP-адреса в двоичные строки, используя inet_pton и распаковать (я знаком с ip2long , однако он ограничен IPv4).

Поначалу этот метод работал нормально, однако вскоре я обнаружил, что получаю неправильный результат, сравнивая любой IP-адрес, заканчивающийся на .32, с более низким IP-адресом. Например, если я сравниваю 192.168.1.0 с 192.168.1.32, мой сценарий сообщает мне, что 192.168.1.0 больше, чем 192.168.1.32. Это происходит только тогда, когда один из IP-адресов заканчивается на .32. Первые три октета IP-адреса могут быть изменены, и результат будет таким же.

Следующий код PHP генерирует страницу, иллюстрирующую эту проблему:     

// Loop through every possible last octet, starting with zero
for ($i = 0; $i <= 255; $i++) {

    // Define two IPs, with second IP increasing on each loop by 1
    $IP1 = "192.168.1.0";
    $IP2 = "192.168.1.".$i;

    // Convert each IP to a binary string
    $IP1_bin = current(unpack("A4",inet_pton($IP1)));
    $IP2_bin = current(unpack("A4",inet_pton($IP2)));

    // Convert each IP back to human readable format, just to show they were converted properly
    $IP1_string = inet_ntop(pack("A4",$IP1_bin));
    $IP2_string = inet_ntop(pack("A4",$IP2_bin));

    // Compare each IP and echo the result
    if ($IP1_bin < $IP2_bin) {echo '<p>'.$IP1_string.' is LESS than '.$IP2_string.'</p>';}
    if ($IP1_bin === $IP2_bin) {echo '<p>'.$IP1_string.' is EQUAL to '.$IP2_string.'</p>';}
    if ($IP1_bin > $IP2_bin) {echo '<p>'.$IP1_string.' is GREATER than '.$IP2_string.'</p>';}

    // I have also tried using strcmp for the binary comparison, with the same result
    // if (strcmp($IP1_bin,$IP2_bin) < 0) {echo '<p>'.$IP1_string.' is LESS than '.$IP2_string.'</p>';}
    // if (strcmp($IP1_bin,$IP2_bin) === 0) {echo '<p>'.$IP1_string.' iS EQUAL to '.$IP2_string.'</p>';}
    // if (strcmp($IP1_bin,$IP2_bin) > 0) {echo '<p>'.$IP1_string.' is GREATER than '.$IP2_string.'</p>';}
}

?>

Вот пример результата:

192.168.1.0 is EQUAL to 192.168.1.0
192.168.1.0 is LESS than 192.168.1.1
192.168.1.0 is LESS than 192.168.1.2
192.168.1.0 is LESS than 192.168.1.3
192.168.1.0 is LESS than 192.168.1.4
...
192.168.1.0 is LESS than 192.168.1.31
192.168.1.0 is GREATER than 192.168.1.32
192.168.1.0 is LESS than 192.168.1.33
...

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

Любая помощь в определении причины этого будет принята с благодарностью. Я не настроен на использование методов преобразования и сравнения IP, показанных в примере сценария, однако мне нужно придерживаться методов, которые поддерживают как IPv4, так и IPv6. Спасибо.

Я использую PHP версии 5.3.3 с Zend Engine v2.3.0 и PHP-загрузчиком ionCube v4.6.1

Изменить . Я решил проблему, изменив формат распаковки с "A4" (строки с пробелами) на "a4" (строки с NUL-полями). Смотрите мой ответ ниже для деталей.

4 голоса | спросил Seth McCauley 15 MaramSun, 15 Mar 2015 00:57:37 +03002015-03-15T00:57:37+03:0012 2015, 00:57:37

2 ответа


0

После долгих поисков неисправностей я обнаружил причину этой проблемы. При использовании распаковки я использовал код формата «А4», который для строк с пробелами. Это привело к тому, что все, что заканчивается числом 32, будет рассматриваться как пробел, который затем будет обрезан. Например, после распаковки двоичного значения для 192.168.1.32 я преобразовал его в шестнадцатеричное. Результат был C0A801, но должен был быть C0A80120. Результат был бы еще хуже, если бы IP был 192.32.32.32, так как он обрезался бы до C0 (по сути 192.0.0.0).

Переход к распакованному формату "a4" (строки с добавлением NUL) исправил проблему. Теперь только IP-адреса, которые усекаются, это те, которые заканчиваются на .0, что является ожидаемым поведением. Я нахожу странным, что это решило проблему, так как почти каждый пример, с которым я сталкивался при распаковке адресов IPv4 и IPv6, использовал формат «A4». Возможно, они изменили inet_pton на пробел в новой версии.

ответил Seth McCauley 18 MarpmWed, 18 Mar 2015 19:43:57 +03002015-03-18T19:43:57+03:0007 2015, 19:43:57
0

Если вы хотите преобразовать число вместо двоичного числа под парой полезных функций:

function ipv6ToNum($ip)
{
    $binaryNum = '';
    foreach (unpack('C*', inet_pton($ip)) as $byte) {
        $binaryNum .= str_pad(decbin($byte), 8, "0", STR_PAD_LEFT);
    }
    $binToInt = base_convert(ltrim($binaryNum, '0'), 2, 10);

    return $binToInt;
}

function ipv4ToNum($ip)
{
    $result = 0;
    $ipNumbers = explode('.', $ip);
    for ($i=0; $i < count($ipNumbers); $i++) {
        $power = count($ipNumbers) - $i;
        $result += pow(256, $i) * $ipNumbers[$i];
    }

    return $result;
}

(я повторяю логику ip2long в ipv4ToNum)

ответил nik.longstone 15 MaramSun, 15 Mar 2015 03:15:20 +03002015-03-15T03:15:20+03:0003 2015, 03:15:20

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132