T-SQL 处的子网(或 CIDR)IP 控制

Subnet (or CIDR) IP control at T-SQL

不知道具体怎么解释,查询IP子网的选择比较有问题。例如,有一个 IP 地址列表,我有另一个 CIDR/subnet 掩码列表(X.X.X.0/24 等)。我如何通过 T-SQL 得知第一个列表中的每个 IP 地址都在 CIDR/subnet 掩码列表中?

例如:

IP: 172.28.112.23 -> false

IP: 172.28.111.33 -> 真

IP 列表输出:

子网输出:

您想完全按照计算机的方式来确定 IP 地址是否在子网中 - 即:

1) 将网络地址、子网掩码和测试地址转换为二进制。

2) 检查是否(网络地址和子网掩码)=(测试地址和子网掩码)
(&代表按位与)
如果此比较为真,则测试地址在子网内

理解这一点的关键是要认识到 IP 地址(和子网掩码)只是 32 位数字。
按位和在 2 个 32 位数字之间创建一个新的 32 位数字,在被比较的两个数字中都为 1 的位置为 1,否则为 0。

EG: 1010 & 1100 = 1000 因为两个数字中的第一个数字都是 1(在第一个数字的结果中产生 1),但是第 2 个第 3 个和第 4 个数字不是(所以在结果中给出 0对于第 2、3 和 4 位数字)。

SQL 不幸的是,服务器无法在 2 个二进制数之间执行按位运算,但它在十进制表示之间工作正常(即,当转换为 BIGINT 数据类型时)。

因此我建议您首先创建一个将您的 IP 地址转换为 BIGINT 数据类型的函数

CREATE FUNCTION dbo.fnIPtoBigInt
(
    @Ipaddress NVARCHAR(15) -- should be in the form '123.123.123.123'
)
RETURNS BIGINT
AS
BEGIN
 DECLARE @part1 AS NVARCHAR(3) 
 DECLARE @part2 AS NVARCHAR(3) 
 DECLARE @part3 AS NVARCHAR(3)
 DECLARE @part4 AS NVARCHAR(3)

 SELECT @part1 = LEFT(@Ipaddress, CHARINDEX('.',@Ipaddress) - 1)
 SELECT @Ipaddress = SUBSTRING(@Ipaddress, LEN(@part1) + 2, 15)
 SELECT @part2 = LEFT(@Ipaddress, CHARINDEX('.',@Ipaddress) - 1)
 SELECT @Ipaddress = SUBSTRING(@Ipaddress, LEN(@part2) + 2, 15)
 SELECT @part3 = LEFT(@Ipaddress, CHARINDEX('.',@Ipaddress) - 1)
 SELECT @part4 = SUBSTRING(@Ipaddress, LEN(@part3) + 2, 15)

 DECLARE @ipAsBigInt AS BIGINT
 SELECT @ipAsBigInt =
    (16777216 * (CAST(@part1 AS BIGINT)))
    + (65536 * (CAST(@part2 AS BIGINT)))
    + (256 * (CAST(@part3 AS BIGINT)))
    + (CAST(@part4 AS BIGINT))

 RETURN @ipAsBigInt

END

GO

那么你就可以轻松实现一个函数来测试一个地址是否在一个子网中:

CREATE FUNCTION dbo.fnIsIpaddressInSubnet
(
    @networkAddress NVARCHAR(15), -- 'eg: '192.168.0.0'
    @subnetMask NVARCHAR(15), -- 'eg: '255.255.255.0' for '/24'
    @testAddress NVARCHAR(15) -- 'eg: '192.168.0.1'
)
RETURNS BIT AS
BEGIN
    RETURN CASE WHEN (dbo.fnIPtoBigInt(@networkAddress) & dbo.fnIPtoBigInt(@subnetMask)) 
        = (dbo.fnIPtoBigInt(@testAddress) & dbo.fnIPtoBigInt(@subnetMask)) 
    THEN 1 ELSE 0 END
END

为了使这对您来说更容易一些,您可能需要一个也可以将 '/24' 转换为 BigInt 的函数。
'/24' 是 shorthand 255.255.255.0 的写法 - 即一个 32 位数字,前 24 位设置为 1(其余 8 位设置为 0)

CREATE FUNCTION dbo.fnSubnetBitstoBigInt
(
    @SubnetBits TINYINT -- max = 32
)
RETURNS BIGINT
AS
BEGIN

 DECLARE @multiplier AS BIGINT = 2147483648
 DECLARE @ipAsBigInt AS BIGINT = 0
 DECLARE @bitIndex TINYINT = 1
 WHILE @bitIndex <= @SubnetBits
 BEGIN
    SELECT @ipAsBigInt = @ipAsBigInt + @multiplier
    SELECT @multiplier = @multiplier / 2
    SELECT @bitIndex = @bitIndex + 1
 END

 RETURN @ipAsBigInt

END

GO

如果您创建以下附加函数,转换就会变得容易

CREATE FUNCTION dbo.fnIsIpaddressInSubnetShortHand
(
    @network NVARCHAR(18), -- 'eg: '192.168.0.0/24'
    @testAddress NVARCHAR(15) -- 'eg: '192.168.0.1'
)
RETURNS BIT AS
BEGIN
    DECLARE @networkAddress NVARCHAR(15)
    DECLARE @subnetBits TINYINT

    SELECT @networkAddress = LEFT(@network, CHARINDEX('/', @network) - 1)
    SELECT @subnetBits = CAST(SUBSTRING(@network, LEN(@networkAddress) + 2, 2) AS TINYINT)

    RETURN CASE WHEN (dbo.fnIPtoBigInt(@networkAddress) & dbo.fnSubnetBitstoBigInt(@subnetBits)) 
        = (dbo.fnIPtoBigInt(@testAddress) & dbo.fnSubnetBitstoBigInt(@subnetBits)) 
    THEN 1 ELSE 0 END
END

SELECT dbo.fnIsIpaddressInSubnetShorthand('192.168.2.0/24','192.168.3.91') -- returns 0
SELECT dbo.fnIsIpaddressInSubnetShorthand('192.168.2.0/24','192.168.2.91') -- returns 1

这本身并不是一个答案,而是一种使 中的函数之一更易于阅读并且可能更高效的方法。

SQL服务器有一个函数来处理从数据库对象名称中获取部分。这些名字有 4 个部分 [Server].[Database].[Schema].[Object]。因此,以下内容允许您获取架构名称。索引从右开始

SELECT PARSENAME('[myServer].[master].[sys].[objects]', 2)

没有什么可以说您不能将其用于 IP 地址。由于它对 SQL 的运作方式如此重要,我认为它已经过优化。

CREATE FUNCTION dbo.fnIPtoBigInt
(
    @Ipaddress NVARCHAR(15) -- should be in the form '123.123.123.123'
)
RETURNS BIGINT
AS
BEGIN
 DECLARE @ipAsBigInt AS BIGINT
 SELECT @ipAsBigInt =
    (16777216 * (CAST(PARSENAME(@Ipaddress, 4) AS BIGINT)))
    + (65536 * (CAST(PARSENAME(@Ipaddress, 3) AS BIGINT)))
    + (256 * (CAST(PARSENAME(@Ipaddress, 2) AS BIGINT)))
    + (CAST(PARSENAME(@Ipaddress, 1) AS BIGINT))

 RETURN @ipAsBigInt

END

GO

完整的解决方案

CREATE
OR ALTER FUNCTION dbo.IPv4SubnetContainsIPAddress (
  @net AS VARCHAR(15),
  @mask AS VARCHAR(15),
  @ip AS VARCHAR(15)
) RETURNS tinyint AS BEGIN DECLARE @result AS tinyint IF LEN(@mask) <= 2
SELECT
  @mask = m
FROM
  (
    VALUES
      (0, '0.0.0.0'),
      (1, '128.0.0.0'),
      (2, '192.0.0.0'),
      (3, '224.0.0.0'),
      (4, '240.0.0.0'),
      (5, '248.0.0.0'),
      (6, '252.0.0.0'),
      (7, '254.0.0.0'),
      (8, '255.0.0.0'),
      (9, '255.128.0.0'),
      (10, '255.192.0.0'),
      (11, '255.224.0.0'),
      (12, '255.240.0.0'),
      (13, '255.248.0.0'),
      (14, '255.252.0.0'),
      (15, '255.254.0.0'),
      (16, '255.255.0.0'),
      (17, '255.255.128.0'),
      (18, '255.255.192.0'),
      (19, '255.255.224.0'),
      (20, '255.255.240.0'),
      (21, '255.255.248.0'),
      (22, '255.255.252.0'),
      (23, '255.255.254.0'),
      (24, '255.255.255.0'),
      (25, '255.255.255.128'),
      (26, '255.255.255.192'),
      (27, '255.255.255.224'),
      (28, '255.255.255.240'),
      (29, '255.255.255.248'),
      (30, '255.255.255.252'),
      (31, '255.255.255.254'),
      (32, '255.255.255.255')
  ) AS o (i, m)
WHERE
  i = @mask
SELECT
  @result = IIF(Count(*) = 4, 1, 0)
FROM
  (
    SELECT
      *,
      IIF(
        o_ip BETWEEN o_subnet
        AND o_broadcast,
        1,
        0
      ) AS eq
    FROM
      (
        SELECT
          *,
          o_net & o_mask AS o_subnet,
          o_net | (255 - o_mask) AS o_broadcast
        FROM
          (
            SELECT
              o_net,
              o_mask,
              o_ip
            FROM
              (
                VALUES
                  (1, CAST(PARSENAME(@net, 4) AS INTEGER)),
                  (2, CAST(PARSENAME(@net, 3) AS INTEGER)),
                  (3, CAST(PARSENAME(@net, 2) AS INTEGER)),
                  (4, CAST(PARSENAME(@net, 1) AS INTEGER))
              ) AS c1 (i, o_net)
              LEFT JOIN (
                SELECT
                  i,
                  o_mask
                FROM
                  (
                    VALUES
                      (1, CAST(PARSENAME(@mask, 4) AS INTEGER)),
                      (2, CAST(PARSENAME(@mask, 3) AS INTEGER)),
                      (3, CAST(PARSENAME(@mask, 2) AS INTEGER)),
                      (4, CAST(PARSENAME(@mask, 1) AS INTEGER))
                  ) AS c2 (i, o_mask)
              ) AS c2 ON c1.i = c2.i
              LEFT JOIN (
                SELECT
                  i,
                  o_ip
                FROM
                  (
                    VALUES
                      (1, CAST(PARSENAME(@ip, 4) AS INTEGER)),
                      (2, CAST(PARSENAME(@ip, 3) AS INTEGER)),
                      (3, CAST(PARSENAME(@ip, 2) AS INTEGER)),
                      (4, CAST(PARSENAME(@ip, 1) AS INTEGER))
                  ) AS c3 (i, o_ip)
              ) AS c3 ON c1.i = c3.i
          ) AS t
      ) AS t
  ) AS t
WHERE
  eq = 1 RETURN @result END
GO
SELECT
  dbo.IPv4SubnetContainsIPAddress('192.168.64.0', '255.255.224.0', '192.168.40.1') -- returns 0
SELECT
  dbo.IPv4SubnetContainsIPAddress('192.168.64.0', '19', '192.168.40.1') -- returns 0
SELECT
  dbo.IPv4SubnetContainsIPAddress('192.168.64.0', '255.255.192.0', '192.168.80.1') -- returns 1
SELECT
  dbo.IPv4SubnetContainsIPAddress('192.168.64.0', '18', '192.168.80.1') -- returns 1