ProfiPHPProfiPHP
  1. Главная
  2. Полезное в PHP
  3. Работа с IPv6 в PHP

Работа с IPv6 в PHP

Очень часто для большинства проектов возникает необходимость работы с IP адресами. В те времена, когда существовал только IP версии 4, для упрощения работы использовались две функции Ip2long и Long2ip, которые переводили IP адрес в обычное число и обратно. Данная реализация позволяла экономично использовать память и ресурсы системы. Но в IP версии 6 нет такой возможности.

Для начала 32 бита, из которых состоит IPv4, вполне было достаточно. 32 битные программы поддерживают работу с числами от 0 до 4294967295 (или 2 в 32 степени), что является максимальным количеством IP адресов в сети. Самое большое беззнаковое число допустимое на 64bit платформе - 2 в 64 степени. Но IPv6 пошел еще далее, допуская количество адресов 2 в 128 степени, что иногда приводит к проблемам работы с памятью и неверным результатам.

Для правильной и безопасной проверки валидации IPv4 и IPv6 в PHP существует функция Filter_var, которая осуществляет проверку входящих данных. Для валидации IP адреса, следует использовать фильтр FILTER_VALIDATE_IP. А для определения версии - флаги FILTER_FLAG_IPV4 или FILTER_FLAG_IPV6.

Для преобразования IPv6 существуют функции Inet_pton и Inet_ntop, которые преобразуют человеко-понятное представление IP адреса в упакованное In_addr представление, и обратно. Поскольку результат выполнения функции Inet_pton не является чисто бинарным, необходимо воспользоваться функцией Unpack чтобы в дальнейшем можно было работать с битовыми операциями. Также можно использовать функцию Bin2hex, которая преобразует бинарные данные в шестнадцатиричное представление.

Для разных версий IP адресов необходимо использовать разные методы распаковывания результата. Для IPv6 будем использовать формат A16, а для IPv4 - A4. Формат A4 означает строку (string) со SPACE-заполнением и 4-мя аргументами повторения. Аналогично с A16.

Давайте рассмотрим это на примере:
$ip4 = "44.67.85.126";
$ip6 = "FE80:0000:0000:0000:0202:B3FF:FE1E:8329";
// Пример с Ip2long
var_dump( ip2long( $ip4 ) ); // int(742610302)
var_dump( ip2long( $ip6 ) ); // bool(false)

// Пример с Inet_pton
var_dump( inet_pton( $ip4 ) ); // string(4) ",CU~"
var_dump( inet_pton( $ip6 ) ); // string(16) "���������)"
Теперь необходимо преобразовать упакованный результат в распакованное значение:
$u4 = current( unpack( "A4", inet_pton( $ip4 ) ) );
var_dump( inet_ntop( pack( "A4", $u4 ) ) ); // string(12) "44.67.85.126"

$u6 = current( unpack( "A16", inet_pton( $ip6 ) ) );
var_dump( inet_ntop( pack( "A16", $u6 ) ) ); // string(24) "fe80::202:b3ff:fe1e:8329"
Функция Current возвращает первый элемент массива.

В результате данные после распаковывания и упаковывания сохраняются одинаковыми.

Опишем функции, которые выполняют выше описанные операции. Функция для преобразования IP адреса в бинарный вид:
function iptobin( $ip ){
if ( filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 ) ) {
return current( unpack( "A4", inet_pton( $ip ) ) );
}
elseif ( filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 ) ) {
return current( unpack( "A16", inet_pton( $ip ) ) );
}
else {
return false;
}
}
Функция для обратного преобразования:
function bintoip( $str ) {
if ( strlen( $str ) == 16 || strlen( $str ) == 4 ) {
return inet_ntop( pack( "A".strlen( $str ), $str ) );
}
else {
return false;
}
}
Функция Iptobin проверяет входящее значение и возвращает преобразованное значение или False в случае возникновения ошибки. Функция Bintoip преобразует результат выполнения Iptobin обратно, проверяя входящее значение на соответствие форматам A4/A16 и возвращая результат, или False в случае возникновения ошибки.

Для сохранения бинарных файлов в базе данных можно использовать человекочетаемый вид в формате VARCHAR(39). Но рекомендуется все же сохранять в бинарном виде используя BINARY(16), что позволит делать выборки по диапазонам, битовым маскам и предоставлять другие преимущества, для построения приложения поддерживающего IPv6.

Проведем эксперимент. Создадим таблицу с полем Varchar(39). В данный диапазон символов полностью поместиться Ipv6 в открытом виде. Для Ipv6 достаточно всего 15 символов (вместе с точками), которые вписываются в данное поле. И запишем в данную таблицу 10000000 записей с разными IP адресами вперемешку. Для этого будем две функции генерации случайного IP6 и IP4 адресов:
<?php
function generate_ipv6_generator()
{
$ip6 = '';
for ($i = 0; $i < 8; $i++) {
$seed = str_split('1234567890abcdef');
shuffle($seed);
$block = join("", $seed);
$block = substr($block, 0, 4);
$ip6 .= ($i != 7 ) ? $block.':' : $block;
}
return $ip6;
}
function generate_ipv4_generator()
{
return mt_rand(0, 255).'.'.mt_rand(0, 255).'.'.mt_rand(0, 255).'.'.mt_rand(0, 255);
}
$array_insert = array( );
for ($i = 1; $i <= 10000000; $i++)
{
$ip = mt_rand(0, 1) ? generate_ipv6_generator() : generate_ipv4_generator();
$array_insert[] = "('".$ip."')";
if ($i % 1000 == 0)
{
$db->query( "INSERT INTO `table` (`ip`) VALUES ".implode( ', ', $array_insert));
$array_insert = array( );
$db->free( );
}
}
Размер таблицы зависит от данных версий адресов, но при данной реализации он составил 440 мегабайт.

Далее мы будем выполнять операции поиска существования IP адресов и время, потраченное на данную операцию. Более подробно можно почитать в статье: Время выполнения скрипта. Проход по всем значениям составляет 1.876 сек.

А теперь создадим таблицу с полем IP и типом VARBINARY(16):
`ip` VARBINARY(16) NULL DEFAULT NULL
Запись в таблицу будет осуществляться функцией UNHEX:
$array_insert[] = "(UNHEX('".$ip."'))";
Таблица с 10000000 записями во втором варианте будет занимать 326 мегабайт. Поиск по всей таблице будет занимать 1.775 сек.

Как видно из примеров, для уменьшения места для хранения IP адресов необходимо использовать тип поля VARBINARY(16), а для быстроты поиска большой разницы нет.

Добавить комментарий

Имя:
Текст комментария: