ipv6 — Как сравнить 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-адрес, поэтому я считаю, что проблема заключается в сравнении. Я пытался перейти на зЬгстр для двоичного сравнения, однако, результат был тем же.

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

Я использую PHP версии 5.3.3 с Zend Engine v2.3.0 и ionCube PHP Loader v4.6.1

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

4

Решение

После долгих поисков неисправностей я обнаружил причину этой проблемы. При использовании распаковывать функция, я использовал код формата «А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 на пробел в более новой версии.

3

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

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

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)

1

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