Как преобразовать IPv6 из двоичного для хранения в MySQL
Вопрос
Я пытаюсь эффективно хранить адреса IPv6 в MySQL 5.0.Я прочитал другие вопросы, связанные с этим, такой как этот.Автор этого вопроса в конечном итоге выбрал два поля BIGINT.Мои поиски также выявили еще один часто используемый механизм:Использование DECIMAL(39,0) для хранения адреса IPv6.У меня есть два вопроса по этому поводу.
- Каковы преимущества и недостатки использования DECIMAL(39,0) по сравнению с другими методами, такими как 2*BIGINT?
- Как мне преобразовать (в PHP) из двоичного формата, возвращаемого inet_pton() в формат десятичной строки, используемый MySQL, и как мне выполнить обратное преобразование, чтобы можно было красиво печатать с помощью inet_ntop()?
Решение 2
Вот функции, которые я сейчас использую для преобразования IP-адресов из и в формат DECIMAL (39,0).Они называются inet_ptod и inet_dtop, что означает «от представления до десятичного числа» и «от десятичного представления до представления».Требуется поддержка IPv6 и bcmath в PHP.
/**
* Convert an IP address from presentation to decimal(39,0) format suitable for storage in MySQL
*
* @param string $ip_address An IP address in IPv4, IPv6 or decimal notation
* @return string The IP address in decimal notation
*/
function inet_ptod($ip_address)
{
// IPv4 address
if (strpos($ip_address, ':') === false && strpos($ip_address, '.') !== false) {
$ip_address = '::' . $ip_address;
}
// IPv6 address
if (strpos($ip_address, ':') !== false) {
$network = inet_pton($ip_address);
$parts = unpack('N*', $network);
foreach ($parts as &$part) {
if ($part < 0) {
$part = bcadd((string) $part, '4294967296');
}
if (!is_string($part)) {
$part = (string) $part;
}
}
$decimal = $parts[4];
$decimal = bcadd($decimal, bcmul($parts[3], '4294967296'));
$decimal = bcadd($decimal, bcmul($parts[2], '18446744073709551616'));
$decimal = bcadd($decimal, bcmul($parts[1], '79228162514264337593543950336'));
return $decimal;
}
// Decimal address
return $ip_address;
}
/**
* Convert an IP address from decimal format to presentation format
*
* @param string $decimal An IP address in IPv4, IPv6 or decimal notation
* @return string The IP address in presentation format
*/
function inet_dtop($decimal)
{
// IPv4 or IPv6 format
if (strpos($decimal, ':') !== false || strpos($decimal, '.') !== false) {
return $decimal;
}
// Decimal format
$parts = array();
$parts[1] = bcdiv($decimal, '79228162514264337593543950336', 0);
$decimal = bcsub($decimal, bcmul($parts[1], '79228162514264337593543950336'));
$parts[2] = bcdiv($decimal, '18446744073709551616', 0);
$decimal = bcsub($decimal, bcmul($parts[2], '18446744073709551616'));
$parts[3] = bcdiv($decimal, '4294967296', 0);
$decimal = bcsub($decimal, bcmul($parts[3], '4294967296'));
$parts[4] = $decimal;
foreach ($parts as &$part) {
if (bccomp($part, '2147483647') == 1) {
$part = bcsub($part, '4294967296');
}
$part = (int) $part;
}
$network = pack('N4', $parts[1], $parts[2], $parts[3], $parts[4]);
$ip_address = inet_ntop($network);
// Turn IPv6 to IPv4 if it's IPv4
if (preg_match('/^::\d+.\d+.\d+.\d+$/', $ip_address)) {
return substr($ip_address, 2);
}
return $ip_address;
}
Другие советы
Мы пошли на VARBINARY(16)
вместо этого столбец и используйте inet_pton()
и inet_ntop()
чтобы выполнить преобразования:
https://github.com/skion/mysql-udf-ipv6
Функции могут быть загружены на работающий сервер MySQL и дадут вам INET6_NTOP
и INET6_PTON
в SQL, так же, как знакомый INET_NTOA
и INET_ATON
функции для IPv4.
Редактировать:Теперь в MySQL есть совместимые функции, просто с другой имена.Используйте вышеизложенное только в том случае, если вы используете MySQL до версии 5.6 и ищете удобный путь обновления в будущем.
ДЕСЯТИЧНЫЙ(39)
Плюсы:
- Работает с основными арифметическими операторами (такими как + и -).
- Работает с базовой индексацией (точной или диапазонной).
- Формат удобен для отображения.
Минусы:
- Может принимать значения, выходящие за пределы диапазона IPv6.
- Это не очень эффективный механизм хранения.
- Может вызвать путаницу относительно того, какие математические операторы или функции работают, а какие нет.
ДВОИЧНЫЙ(16)...
Плюсы:
- Самый эффективный формат для точного представления.
- Работает с базовой индексацией (точной и диапазонной).
- Работает с префиксной индексацией для префиксов, кратных 8 битам.
- Сохраняет только действительные значения IPv6 (хотя и не гарантирует правильную адресацию).
- MySQL в более поздних версиях имеет функции, которые поддерживают преобразования этого формата в представления IPv6 и обратно (но не 4in6).
Минусы:
- Не дружелюбно для показа.
- Несовместим с операторами или функциями, предназначенными для чисел.
ДВОИЧНЫЙ(39)...
Это для полных адресов (используя hexdec даже для 4in6).Также может быть ascii, а не двоичный.
Плюсы:
- Читабельный для человека (если можно так назвать IPv6).
- Поддерживает базовую индексацию (точную и диапазонную).
- Поддерживает префиксную индексацию, кратную 4 битам.
- Непосредственная совместимость с IPv6.Никаких преобразований не требуется.
Минусы:
- Не очень хорошо работает с математическими функциями или операторами.
- Самое неэффективное хранилище.
- Может допускать недопустимые представления.
Странности:
- Становится сложным, если вам нужны такие вещи, как нечувствительность к регистру.
- В IPv6 есть и другие форматы отображения, хотя их использование создает дополнительные сложности, например, вы можете иметь два представления одного и того же адреса или потерять поиск по диапазону.Может даже потребоваться сделать его длиной 45 байт или использовать varchar/varbinary.
- Варианты этого могут способствовать сохранению первоначально полученного адреса.Это редко бывает желательно, но в этом случае вы теряете много преимуществ.
- Удалите разделители в полном формате и просто сохраните его в виде шестнадцатеричной строки, чтобы было меньше проблем и немного больше эффективности.Вы можете пойти по этому пути, если важна индексация префикса (BINARY(128)).
БИГИНТ БЕЗЗНАКА * 2
Плюсы:
- Работает с математическими операторами и функциями с оговоркой о необходимости делать дополнительные действия вокруг двух столбцов.
- Эффективно, но опять же с оговоркой, что наличие двух столбцов приведет к некоторым накладным расходам.
- Работает с базовыми индексами (точный, диапазон).
- Работает с префиксным индексом, если префикс имеет длину 64 бита.
- Отображать удобный формат.
Минусы:
- Два столбца делают его неатомарным и означают удвоение множества операций над ним.
Странности:
- Многие современные языки и системы предоставляют 64-битные целые числа, но не беззнаковые.Подписать проблематично.Отрицательные числа представляются меньшими, чем положительные, но их битовые последовательности на самом деле выше.По этой причине вместо этого обычно используется 4 * INT UNSIGNED.
- Точно так же люди могут разбить его на префиксную индексацию, и вы можете дойти как минимум до 8 бит (TINYINT UNSIGNED).Некоторые люди могут также использовать тип BIT(1) для полной префиксной индексации, предполагая, что MySQL правильно совмещает индексы с битовыми типами.
- Опять же, аналогично с четырьмя столбцами, некоторые операции, которые требуют таких вещей, как перенос из одного столбца в другой, по иронии судьбы проще из-за нехватки битов во время вычислений (промежуточные значения в вычислениях все еще могут быть 64-битными).
Краткое содержание
Люди будут использовать разные форматы по разным причинам.Одной из причин может быть обратная совместимость, и это зависит от того, что делалось для IPv4.Другие зависят от того, как используются адреса, и от оптимизации, связанной с этим.Вы можете увидеть, что используется более одного подхода.
B16 — хороший подход по умолчанию, поскольку он наиболее эффективен и беспроблемен.
Конверсии в PHP вы можете сделать вручную, если изучите:
- gmp или bcmath
- Обработка чисел и побитовые операторы PHP, особенно обратите внимание на ограничения на int или float, а также на зависящие от них функции, которые в противном случае могли бы показаться полезными.
- Форматы IPv6
- упаковать/распаковать, bin2hex/hex2bin.
Однако я бы рекомендовал использовать общую библиотеку для работы с различными форматами отображения IPv6.