Subnet contains IP? Вот в чем вопрос!

Доброго!

Сегодня маленький скриптик, который поможет вычислять входит ли IP в подсеть.

Код скрипта

Function Compare-IPinSubnet {
param (
	[string]$CIDR,
	[string]$IP
)

	[int]$PrefixLength = $CIDR -replace '^[\d\.]+(\\|\/)',''
	[string]$Subnet = $CIDR -replace '(\\|\/)\d+$',''
	$SplitSubnetBin = $Subnet -split '\.' -match '\d' | % {[convert]::ToString($_,2).PadLeft(8,'0')}
	$SubnetBin = $SplitSubnetBin -join ''
	$SplitIPBin = $IP -split '\.' -match '\d' | % {[convert]::ToString($_,2).PadLeft(8,'0')}
	$IPBin = $SplitIPBin -join ''
	[bool]$Result = $true
	for ($i = 0; $i -lt $PrefixLength; $i += 1) {
		[bool]$diff = [convert]::ToInt32(($SubnetBin[$i]),2) -eq [convert]::ToInt32(($IPBin[$i]),2)
		if ($diff -eq $false) {
			$Result = $diff
		}
	}

	$Result
}

Как это можно использовать

Примеры простого запуска:

PS> Compare-IPSubnet -CIDR 8.8.0.0/16 -IP 8.8.8.8
True
PS>

Или так:

PS> Compare-IPSubnet -CIDR 8.8.0.0/16 -IP 8.9.8.8
False
PS>

А вот прикладная задача, находим все маршруты для IP:

PS> Get-NetRoute -AddressFamily IPv4 | ? {Compare-IPSubnet -CIDR $_.DestinationPrefix -IP 8.8.8.8}

ifIndex DestinationPrefix                              NextHop                                  RouteMetric ifMetric PolicyStore
------- -----------------                              -------                                  ----------- -------- -----------
8       8.8.0.0/16                                     192.168.0.201                                       1 25       ActiveStore
8       0.0.0.0/0                                      192.168.0.1                                         0 25       ActiveStore

PS>

Еще немного кода и находим лучший маршрут для IP:

PS> Get-NetRoute -AddressFamily IPv4 |
>>  ? {Compare-IPSubnet -CIDR $_.DestinationPrefix -IP 8.8.8.8} |
>>  Select-Object -Property DestinationPrefix,NextHop,@{n='PrefixLength'; e = {$_.DestinationPrefix -replace '^[\d\.]+(\\|\/)',''}} |
>>  Sort-Object -Property PrefixLength -Descending |
>>  Select-Object -First 1

DestinationPrefix NextHop      PrefixLength
----------------- -------      ------------
8.8.0.0/16        192.168.0.201 16


PS>

Внутренности

Вероятно можно было придумать алгоритм попроще, но мне нужно было решить задачу по работе, и не было времени на оптимизацию 🙂

Я выбрал следующий алгоритм.
Для разъяснения воспользуемся моим скриптом IP-Calc

PS> IP-Calc -CIDR 192.168.0.27/25


IP           : 192.168.0.27
Mask         : 255.255.255.128
PrefixLength : 25
WildCard     : 0.0.0.127
IPcount      : 128
Subnet       : 192.168.0.0
Broadcast    : 192.168.0.127
CIDR         : 192.168.0.0/25
ToDecimal    : 3232235547
IPBin        : 11000000.10101000.00000000.00011011
MaskBin      : 11111111.11111111.11111111.10000000
SubnetBin    : 11000000.10101000.00000000.00000000
BroadcastBin : 11000000.10101000.00000000.01111111



PS>

Нужно понимать что такое маска и как она работает.

В данном случае маска имеет длину 25 бит, то есть в поле MaskBin у нас 25 единиц и 7 нулей.
Назначение сетевой маски заключается в разделении сетевой и хостовой части, там, где единицы — сетевая часть, там, где нули — хостовая.
Поскольку хостов в подсети может быть много, именно хостовая часть IP адреса может иметь разные значения, а вот сетевая часть адреса меняться не может. То есть там где в маске единицы, все биты ip адреса должны совпадать с битами сети, иначе этот адрес не будет входить в подсеть.

Действительно, в данном примере у нас в маске (MaskBin) 25 — единиц, поэтому для расчетов нам достаточно сравнить именно первые 25 бит ip адреса (IPBin) с теми же битами подсети (SubnetBin):
IPBin             : 11000000.10101000.00000000.00011011
MaskBin      : 11111111.11111111.11111111.10000000
SubnetBin  : 11000000.10101000.00000000.00000000

Первые 25 бит у нас совпали, значит ip адрес 192.168.0.27 входит в подсеть 192.168.0.0/25 и далее проверять смысла нет.

Собственно, так я и сделал, организовав цикл от нуля до «длины маски» (не включая последнее значение), т.е. если длина 25, то цикл пойдет от 0 до 24 и, если какой-то бит не совпал, в $Result запишется $false, иначе $Result останется $true:

for ($i = 0; $i -lt $PrefixLength; $i += 1) {
		[bool]$diff = [convert]::ToInt32(($SubnetBin[$i]),2) -eq [convert]::ToInt32(($IPBin[$i]),2)
		if ($diff -eq $false) {
			$Result = $diff
		}
	}

Работает скрипт довольно быстро, замер производительности показывает результат около 5 мс, запустим 1000 раз, и видим:

 
PS> Measure-Command { 1..1000 | % {Compare-IPinSubnet -CIDR 192.168.0.150/26 -IP 192.168.0.192}}


Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 446
Ticks             : 4469427
TotalDays         : 5,17294791666667E-06
TotalHours        : 0,00012415075
TotalMinutes      : 0,007449045
TotalSeconds      : 0,4469427
TotalMilliseconds : 446,9427


Как обозвать скрипт не могу придумать, соответственно не могу даже выложить его на powershellgallery, ну и возможно функционал скрипта нужно будет расширить, поживем, увидим.
Пишите комменты, задавайте вопросы!

Реклама

Subnet contains IP? Вот в чем вопрос!: 6 комментариев

  1. Версионность поддерживается, но весьма скудно.
    Да, это больше галлерея. Можно считать, что это скорее не git а apt, если линуксовыми терминами.
    Да, с вложенностью комментов здесь проблема…

    Нравится

    Ответить

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход /  Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход /  Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход /  Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход /  Изменить )

w

Connecting to %s