【问题标题】:How to calculate a IPv6 CIDR route prefix in SQL?如何在 SQL 中计算 IPv6 CIDR 路由前缀?
【发布时间】:2015-04-20 04:11:35
【问题描述】:

我一直致力于使用在 SQL Server 2012 实例上运行的 T-SQL 从 IPv4 和 IPv6 地址范围生成 CIDR。通常我们的应用程序(在数据库之外)负责计算 CIDR,但我目前需要在数据库中进行 CIDR 计算。由于 IPv6 太大而无法存储在 bigint 数据类型中,我们将 IP 地址存储为 binary(4)binary(16)

计算 IPv4 范围的路由前缀虽然有点难看,但相对简单:

declare @ipv4_begin binary(4)
       ,@ipv4_end binary(4)

set @ipv4_begin = 0xC0A80000 -- '192.168.000.000'
set @ipv4_end = 0xC0A8FFFF   -- '192.168.255.255'

select 32 - LOG(
                Cast(@ipv4_end As bigint)
                - Cast(@ipv4_begin As bigint) + 1, 2
               ) as ipv4_route_prefix

遗憾的是,为 IPv6 修改的相同查询不起作用。它不起作用的原因是因为 IPv6 收件人大于 bigint 数据类型中可以存储的内容(我们使用 binary(4)binary(16) 进行存储的原因):

declare @ipv6_begin binary(16)
       ,@ipv6_end binary(16)

set @ipv6_begin = 0xFC000000000000000000000000000000 -- fc00:: 
set @ipv6_end = 0xFC00000000000000FFFFFFFFFFFFFFFF   -- fc00::ffff:ffff:ffff:ffff

-- This will cause error: 'An invalid floating point operation occurred.'
select 128 - LOG(
                 Cast(@ipv6_end As bigint)
                 - Cast(@ipv6_begin As bigint) + 1, 2
                ) as ipv6_route_prefix

除了不可靠的按位运算(最终不起作用)之外,我还没有想出任何可以在数据库中进行此计算的方法。

是否可以根据 T-SQL 中的 IPv6 地址范围计算 IPv6 CIDR 的路由前缀?

【问题讨论】:

  • 虽然上述示例是简单的选择语句,但我对 SQL 服务器函数、公用表表达式、按位运算或任何其他可行的解决方案完全开放。
  • 也许您可以查看一些 C# 解决方案(ipnetwork.codeplex.comstackoverflow.com/q/310599/1080354)并尝试使用 CLR 函数在 SQL Server 中实现它们?
  • @gotqn 我正在使用共享数据库服务器实例。因此,我的 DBA 对添加 CLR 类型犹豫不决(这是理所当然的)。那就是说IPNetwork Utility 虽然有用(我以前用过)不支持 IPv6。

标签: sql-server tsql sql-server-2012 network-programming ipv6


【解决方案1】:

好吧,您已经有了一个巧妙的 IPv4 技巧 - 只需将值分成我们可以处理的最大块并重复该技巧。

SELECT ISNULL(MIN(32 - B + N), 128) 
FROM (VALUES
    (LOG(
        CONVERT(BIGINT, SUBSTRING(@ip_end,    1, 4)) - 
        CONVERT(BIGINT, SUBSTRING(@ip_begin,  1, 4)
        ) + 1, 2),  0),
    (LOG(
        CONVERT(BIGINT, SUBSTRING(@ip_end,    5, 4)) - 
        CONVERT(BIGINT, SUBSTRING(@ip_begin,  5, 4)
        ) + 1, 2), 32),
    (LOG(
        CONVERT(BIGINT, SUBSTRING(@ip_end,    9, 4)) - 
        CONVERT(BIGINT, SUBSTRING(@ip_begin,  9, 4)
        ) + 1, 2), 64),
    (LOG(
        CONVERT(BIGINT, SUBSTRING(@ip_end,   13, 4)) - 
        CONVERT(BIGINT, SUBSTRING(@ip_begin, 13, 4)
        ) + 1, 2), 96)
) AS Bits(B, N)
WHERE B <> 0;

我们确定每个块中第一个设置位的位置,然后选择最低的此类位——如果没有这样的位,则所有位都匹配(ISNULL 涵盖了这种情况)。如果您将“128”替换为“32”,这也适用于 IPv4,但显然您已经有了一个表达式。我们可以将它打包成一个对两者都适用的函数:

CREATE FUNCTION dbo.CidrPrefixFromRange(@ip_begin VARBINARY(16), @ip_end VARBINARY(16)) 
RETURNS TABLE AS
RETURN
    SELECT ISNULL(MIN(32 - B + N), DATALENGTH(@ip_begin) * 8) AS Prefix
    FROM (VALUES
        (LOG(
            CONVERT(BIGINT, SUBSTRING(@ip_end,    1, 4)) - 
            CONVERT(BIGINT, SUBSTRING(@ip_begin,  1, 4)
            ) + 1, 2),  0),
        (LOG(
            CONVERT(BIGINT, SUBSTRING(@ip_end,    5, 4)) - 
            CONVERT(BIGINT, SUBSTRING(@ip_begin,  5, 4)
            ) + 1, 2), 32),
        (LOG(
            CONVERT(BIGINT, SUBSTRING(@ip_end,    9, 4)) - 
            CONVERT(BIGINT, SUBSTRING(@ip_begin,  9, 4)
            ) + 1, 2), 64),
        (LOG(
            CONVERT(BIGINT, SUBSTRING(@ip_end,   13, 4)) - 
            CONVERT(BIGINT, SUBSTRING(@ip_begin, 13, 4)
            ) + 1, 2), 96)
    ) AS Bits(B, N)
    WHERE B <> 0;

示例用途:

-- 192.168.100.0 - 192.168.103.255
SELECT * FROM dbo.CidrPrefixFromRange(0xc0a86400, 0xc0a867ff) -- /22

-- 192.168.0.0 - 192.168.255.255
SELECT * FROM dbo.CidrPrefixFromRange(0xC0A80000, 0xC0A8FFFF) -- /16

-- fc00:: - fc00::ffff:ffff:ffff:ffff
SELECT * FROM dbo.CidrPrefixFromRange(
    0xFC000000000000000000000000000000,
    0xFC00000000000000FFFFFFFFFFFFFFFF
) -- /64

-- 127.0.0.1 - 127.0.0.1
SELECT * FROM dbo.CidrPrefixFromRange(0x7f000001, 0x7f000001) -- /32

没有关于效率如何的承诺......如果您想要效率,这不是您想要在 T-SQL 中做的事情。 :-)

附录:我使用表值函数而不是更简单的标量值函数(毕竟,我们只返回一个值)的原因是标量值函数perform far worse inside a query .内联表值函数可以有效地CROSS APPLY'd 到表中。出于这个原因,我将每个函数都写成内联 TVF 是一种习惯,即使我没有预见到这样的用途——任何东西都比标量值函数更好。

【讨论】:

  • 它以 0x00000000000000000000000000000000 - 0x01000000000000000000000000000000 之类的值失败,我认为它应该返回 8。(sqlfiddle.com/#!6/6b809/3)上限可能会解决这个问题,但我必须先运行它比 SQL 小提琴更复杂的东西
  • @rheone:我假设结束范围都以 1 位结尾,否则没有前缀。在您的示例中,0x000000000000000000000000000000000 - 0x00ffffffffffffffffffffffffffffff。否则,是的,你会遇到舍入问题,因为我们不再处理 2 的整数幂。使函数处理错误输入需要比CEILING 多一点工作,但可以做到。
  • 我有一个替代实现,它以字节为单位分割输入并使用XOR,顺便说一句——它总是返回整数结果,但它仍然不会给出 正确 结果,如果您的范围不能用前缀来描述。 (另外,它并不那么明显。)
  • 你完全正确。我想念阿斯蒂尔正在研究的问题。高地址将始终仅遵循 2^(n)-1 填充到 128 位的模式(0、1、3、7、f,如果以十六进制编码,则后跟 f)。顺便说一句,高位和低位之间的异或运算会导致位掩码。该位掩码可以与高位进行 OR 运算,然后再次反转并与低位进行 AND 运算,以纠正不适合正确 cidr 格式的错误高/低地址,如上所述。但在我们的情况下这应该不是必需的,因为输入是干净的。
  • 干得好,有时这是最好的最难以捉摸的最简单的解决方案。
猜你喜欢
  • 2012-04-22
  • 2023-04-07
  • 2016-09-29
  • 2017-08-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-16
相关资源
最近更新 更多