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

2022-08-31 00:07:25

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

  1. 使用 DECIMAL(39,0) 与其他方法(如 2*BIGINT)相比有哪些优点和缺点?
  2. 如何将(在PHP中)从inet_pton()返回的二进制格式转换为MySQL可用的十进制字符串格式,以及如何转换回来以便使用inet_ntop()进行漂亮打印?

答案 1

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

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

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

编辑:现在MySQL中有兼容的函数,只是名称不同。仅当您使用5.6之前的MySQL并且正在寻找方便的未来升级路径时,才使用上述内容。


答案 2

以下是我现在用于将 IP 地址从 DECIMAL(39,0) 格式转换为 DECIMAL(39,0) 格式的函数。它们被命名为inet_ptod,inet_dtop表示到十进制“和”十进制到表示”。它需要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;
}

推荐