Вы хотите сделать именно то, что компьютер будет делать, чтобы определить, является ли IP-адрес в subnet- а именно:
1) преобразуют сетевой адрес, маску подсети и тестовый адрес в двоичный файл.
2) Проверьте (Network Address & Subnet Mask) = (Тест Адрес маски & Subnet)
(& представляет побитовое И)
Если это сравнение верно тест-адрес в подсети
The ключ к пониманию этого заключается в том, чтобы понять, что IP-адреса (и маски подсети) - это всего лишь 32-разрядные номера.
Побитовое и между 2 32-битными номерами создает новое 32-битное число с 1 в позиции, где было 1 в обоих из двух сравниваемых чисел, и 0 в противном случае.
EG: 1010 & 1100 = 1000, потому что первая цифра равна 1 в обоих числах (в результате получается 1 в результате для первой цифры), но 2-я и 4-я цифры не являются (так что дайте 0 в результате для 2-я и 4-я цифры).
К сожалению, SQL Server не может выполнять побитовое и между двумя двоичными числами, но отлично работает между десятичными представлениями (т.е. при преобразовании в тип данных 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' - сокращенный способ написания 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
Большое спасибо @ james-s. Это очень полезно и полезно для меня. – juniorDev
добро пожаловать @juniorDev –