如何在MySQL中将IPv6从二进制转换为存储

问题描述 投票:16回答:3

我试图以有效的方式在MySQL 5.0中存储IPv6地址。我已经阅读了与此相关的其他问题,such as this one。该问题的作者最终选择了两个BIGINT字段。我的搜索也发现了另一种常用机制:使用DECIMAL(39,0)存储IPv6地址。我有两个问题。

  1. 使用DECIMAL(39,0)比其他方法(例如2 * BIGINT)有什么优缺点?
  2. 如何将(在PHP中)从inet_pton()返回的二进制格式转换为MySQL可用的十进制字符串格式,以及如何转换回来以便我可以使用inet_ntop()进行漂亮打印?
php mysql ipv6
3个回答
30
投票

我们改为使用VARBINARY(16)列并使用inet_pton()inet_ntop()进行转换:

https://github.com/skion/mysql-udf-ipv6

这些函数可以加载到正在运行的MySQL服务器中,并在SQL中为您提供INET6_NTOPINET6_PTON,就像熟悉的INET_NTOAINET_ATON函数一样。

编辑:现在MySQL中有兼容的功能,只需使用different names。如果您使用的是5.6之前的MySQL,并且正在寻找方便的未来升级路径,请仅使用上述内容。


19
投票

以下是我现在用于将IP地址转换为DECIMAL(39,0)格式的函数。它们被命名为inet_ptod和inet_dtop,用于“presentation-to-decimal”和“decimal-to-presentation”。它需要PHP中的IPv6和bcmath支持。

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

0
投票

DECIMAL(39)

优点:

  • 适用于基本算术运算符(例如+和 - )。
  • 使用基本索引(精确或范围)。
  • 格式显示友好。

缺点:

  • 可以接受IPv6的超出范围值。
  • 不是一个非常有效的存储机制。
  • 可能会导致混淆哪些数学运算符或函数有效,哪些无效。

BINARY(16)...

优点:

  • 精确表示的最有效格式。
  • 使用基本索引(精确和范围)。
  • 使用前缀索引作为8位倍数的前缀。
  • 仅存储有效的IPv6值(但不保证有效的寻址)。
  • 在更高版本中的MySQL具有支持这种格式与IPv6表示之间的转换的功能(但不支持4in6)。

缺点:

  • 不友好的显示。
  • 与运营商或用于数字的功能不友好。

BINARY(39)...

这是针对完整地址(即使对于4in6也使用hexdec)。也可以是ascii而不是二进制。

优点:

  • 人类可读(如果你可以调用IPv6)。
  • 支持基本索引(精确和范围)。
  • 支持多个4位的前缀索引。
  • 直接与IPv6兼容。无需转换。

缺点:

  • 对任何数学函数或运算符都不适用。
  • 最低效的存储。
  • 可以允许无效的表示。

奇闻:

  • 如果您需要不区分大小写等内容,则会变得复杂。
  • IPv6具有其他显示格式,尽管使用这些格式会产生更复杂的情况,例如您可以使用相同地址的两种表示形式,或者丢失范围查找。甚至可以最终使它长达45个字节或使用varchar / varbinary。
  • 这种差异可以支持保留最初接收的地址。这可能很少需要,但是当你失去很多好处时。
  • 删除完整格式的分隔符,只需存储为十六进制字符串,减少麻烦,提高效率。如果前缀索引很重要,你可以采取这个方法(BINARY(128))。

BIGINT UNSIGNED * 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的各种显示格式。

© www.soinside.com 2019 - 2024. All rights reserved.