在计算cidr IP范围时我做错了什么?

时间:2017-02-07 13:12:20

标签: sql sql-server

我正在尝试编写一个函数,它将在CIDR格式的IP上输出一些地址信息(在代码下输出):

22

这输出以下内容:

create function dbo.ConvertIpToInt (@Ip as varchar(15))
    returns bigint
    as
    begin
        return (convert(bigint, parsename(@Ip, 1)) +
                convert(bigint, parsename(@Ip, 2)) * 256 +
                convert(bigint, parsename(@Ip, 3)) * 256 * 256 +
                convert(bigint, parsename(@Ip, 4)) * 256 * 256 * 256)
    end
go


create function dbo.ConvertIntToIp (@Int bigint) 
    returns varchar(15)
    as 
    begin
        declare
             @IpHex     varchar(8)
            ,@IpDotted  varchar(15)

        select
             @IpHex = substring(convert(varchar(30), master.dbo.fn_varbintohexstr(@Int)), 11, 8)

        select
            @IpDotted = convert(varchar(3), convert(int, (convert(varbinary, substring(@IpHex, 1, 2), 2)))) + '.' +
                        convert(varchar(3), convert(int, (convert(varbinary, substring(@IpHex, 3, 2), 2)))) + '.' +
                        convert(varchar(3), convert(int, (convert(varbinary, substring(@IpHex, 5, 2), 2)))) + '.' +
                        convert(varchar(3), convert(int, (convert(varbinary, substring(@IpHex, 7, 2), 2))))
        return @IpDotted
    end
go


create function dbo.GetCidrIpRange (@CidrIp varchar(15))
    returns @result table
    (
        CidrIp      varchar(15) not null,
        Mask        int not null,
        LowRange    varchar(15) not null,
        LowIp       varchar(15) not null,
        HighRange   varchar(15) not null,
        HighIp      varchar(15) not null,
        AddressQty  bigint not null
    )
    as
    begin
        declare @Base       bigint  = cast(4294967295 as bigint)
        declare @Mask       int     = cast(substring(@CidrIp, patindex('%/%' , @CidrIP) + 1, 2) as int)
        declare @Power      bigint  = Power(2.0, 32.0 - @Mask) - 1
        declare @LowRange   bigint  = dbo.ConvertIpToInt(left(@CidrIp, patindex('%/%' , @CidrIp) - 1)) & (@Base ^ @Power)
        declare @HighRange  bigint  = @LowRange + @Power

        insert @result
        select
              CidrIp     = @CidrIp
            , Mask       = @Mask
            , LowRange   = @LowRange
            , LowIp      = dbo.ConvertIntToIp(@LowRange)
            , HighRange  = @HighRange
            , HighIp      = dbo.ConvertIntToIp(@HighRange)
            , AddressQty = convert(bigint, power(2.0, (32.0 - @Mask)))
        return
    end
go

select * from dbo.GetCidrIpRange('195.65.254.11/2');

我一直在浏览SO和Google几个小时,我相信CidrIp Mask LowRange LowIp HighRange HighIp AddressQty -------------------------------------------------------------------------------------- 195.65.254.11/2 2 3221225472 192.0.0.0 4294967295 255.255.255.255 1073741824 ConvertIpToInt是正确的。

但是,我期待以下输出:

ConvertIntToIp

有人可以指出我代码中的错误在哪里吗?我一直盯着自己失明,我没有看到它(或者我误解了如何做到这一点)。

1 个答案:

答案 0 :(得分:1)

根据http://www.ipaddressguide.com/cidrhttp://jodies.de/ipcalc?host=195.65.254.11&mask1=2&mask2=,您的计算是正确的。这两个网站之间唯一的不同意见是 jodies.de/ipcalc 页面会删除该范围内的最低和最高(广播)IP地址。

我使用195.65.254.11/2195.65.254.11/24进行了测试。为了使代码正常工作,我需要将dbo.GetCidrIpRang上的输入参数规范更改为VARCHAR(20)(如comment on the question中的@Damien_The_Unbeliever所述。)

关于表现的两个注释:

  1. 对于ConvertIpToIntConvertIntToIp标量UDF,您最好分别使用 INET_AddressToNumber INET_NumberToAddress 函数,包含在SQL# SQLCLR库的免费版本中(我写过,但是嘿,免费:)。这个建议的原因是,与T-SQL UDF不同, deterministic SQLCLR UDF(以及这两者)不会阻止并行计划。

  2. 如果您不想使用SQLCLR路线,那么您至少应该将ConvertIntToIp功能保持为纯数学。没有理由进行所有这些转换和子串。

    CREATE FUNCTION dbo.IPNumberToAddress(@IPNumber BIGINT)
    RETURNS VARCHAR(15)
    WITH SCHEMABINDING
    AS
    BEGIN
        DECLARE @Oct1 BIGINT,
               @Oct2 INT,
               @Oct3 INT;
    
        SET @Oct1 = @IPNumber / (256 * 256 * 256);
        SET @IPNumber -= (@Oct1 * (256 * 256 * 256));
    
        SET @Oct2 = @IPNumber / (256 * 256);
        SET @IPNumber -= (@Oct2 * (256 * 256));
    
        SET @Oct3 = @IPNumber / 256;
        SET @IPNumber -= (@Oct3 * 256);
    
        RETURN CONCAT(@Oct1, '.', @Oct2, '.', @Oct3, '.', @IPNumber);
    END;
    GO
    

    然后:

    SELECT dbo.IPNumberToAddress(3275881995);
    -- 195.65.254.11
    
  3. 对于GetCidrIpRange TVF,您最好将其转换为内联TVF。您可以通过以下方式通过CTE完成多步计算(您只需要将其清理一点/完成它):

    WITH cte1 AS
    (
        SELECT 2 AS [Mask] -- replace with real formula
    ), cte2 AS
    (
        SELECT 999 AS [Base], -- replace with real formula
               POWER(2.0, 32.0 - cte1.[Mask]) - 1 AS [Power],
            cte1.[Mask]
        FROM   cte1
    ), cte3 AS
    (
        SELECT SQL#.INET_AddressToNumber(left(@CidrIp, PATINDEX('%/%' , @CidrIp) - 1))
            & (cte2.[Base] ^ cte2.[Power]) AS [LowRange],
            cte2.[Power],
            cte2.[Mask]
        FROM   cte2
    )
    SELECT @CidrIp AS [CidrIp],
            cte3.[Mask],
            cte3.[LowRange],
            SQL#.INET_NumberToAddress(cte3.[LowRange]) AS [LowIp],
            (cte3.[LowRange] + cte3.[Power]) AS [HighRange],
            SQL#.INET_NumberToAddress(cte3.[LowRange] + cte3.[Power]) AS [HighIp],
            CONVERT(BIGINT, POWER(2.0, (32.0 - cte3.[Mask]))) AS [AddressQty]
    FROM   cte3 c;