С адресами IP version 4 (IPv4) системные администраторы работают в обычном режиме. Если вам когда-либо доводилось иметь дело с адресами IPv4 в сценариях, вы уже имеете представление о связанных с ними сложностях: дело в том, что адреса IPv4 — это не строки, а 32-разрядные числа. К примеру, для того чтобы рассчитать идентификатор сети, нужно применить к адресу IPv4 и маске подсети поразрядный оператор AND. Порой эти расчеты трудно выполнить корректно в сценариях оболочки (пакетных файлах), а также на языках VBScript и JScript. Это связано с анализом строк и порядком байтов. К счастью, при работе в среде PowerShell дело обстоит намного проще, так как в этом случае мы можем использовать класс. NET IPAddress.
Применение класса IPAddress
Класс — это не более чем шаблон для объекта. Полное имя класса. NET IPAddress формулируется так: System.Net.IPAddress. Создать объект IPAddress можно двумя способами. Первый способ предусматривает использование команды New-Object и указание полного имени класса. Например:
$ip = New-Object System.Net.IPAddress (0x1521A8C0)
Данная команда создает объект IPAddress с IPv4-адресом 192.168.33.21. При указании адреса следует придерживаться обратного порядка байтов (почему так, я объясню позднее). Я написал рассматриваемый IP-адрес в шестнадцатеричном формате (с основанием 16), в котором используется префикс 0x, позволяющий без труда выделять четыре октета адреса: 0x15 = 21, 0x21 = 33, 0xA8 = 168 и 0xC0 = 192. Этот адрес можно записать и в десятичном формате (тогда он примет вид 0x1521A8C0 = 354527424).
Среда PowerShell предусматривает более простой способ работы с IP-адресами: мы можем использовать акселератор типов [IPAddress]. Кроме того, мы можем указывать в скобках полное имя класса, [System.Net.IPAddress], но PowerShell позволяет нам сокращать его до [IPAddress]. Однако дело не только в более коротком имени. Применяя акселератор типов, мы получаем возможность указывать IPv4-адрес не в виде числа, а в формате четырехбайтного адреса dotted-quad, с десятичным представлением каждого байта и межбайтным разделителем в виде точки. Иначе говоря, две приведенные ниже команды функционально эквивалентны:
$ip = New-Object System.Net.IPAddress (0x1521A8C0) $ip = [IPAddress] "192.168.33.21"
На экране 1 показано несколько команд PowerShell, создающих объект IPAddress и отображающих его свойства. Здесь вы видите четыре отдельные команды. Первая команда создает объект IPAddress и назначает его переменной $ip, а вторая переводит объект в категорию выходных данных. Третья команда с помощью оператора -f выводит свойство объекта Address в виде шестнадцатеричной строки, а четвертая выводит свойство объекта IPAddressToString.
Экран 1. Использование класса .NET IPAddress |
Расчет идентификатора сети
Теперь, когда нам известно, каким образом используется класс IPAddress, мы можем с легкостью вычислить идентификатор сети для адреса IPv4 и маски подсети. Как вам, возможно, известно, мы можем рассчитать идентификатор сети, объединяя адрес IPv4 и его маску подсети с помощью оператора -band (поразрядное AND), как показано на экране 2. Первая и вторая команды здесь создают объекты IPAddress как для IPv4-адреса (192.168.33.21), так и для маски подсети (255.255.255.252). Третья команда объединяет свойства Address (то есть численные значения адресов IPv4) с помощью оператора -band и создает тем самым третий объект IPAddress. Наконец, четвертая команда отображает третий объект IPAddress, содержащий идентификатор сети (192.168.33.20).
Экран 2. Расчет идентификатора сети с помощью оператора -band |
Расчет сетевого идентификатора компьютера
В листинге 1 представлен практический пример, содержащий демонстрационный сценарий, который показывает, как рассчитывается текущий сетевой идентификатор компьютера.
С помощью команды Get-WmiObject сценарий выбирает все оснащенные средствами IP объекты Win32_NetworkAdapterConfiguration и для их перебора использует оператор foreach. Затем сценарий использует цикл for для вычисления методом последовательных приближений свойства IPAddress (которое представляет собой строковый массив, отдельный от класса. NET IPAddress) объекта Win32_NetworkAdapterConfiguration. С помощью оператора -match и регулярного выражения сценарий определяет, относится ли данный адрес к категории IPv4-адресов. Если адрес относится к упомянутой категории, сценарий создает объекты. NET IPAddress для IPv4-адреса, маски и идентификатора сети и затем выводит их.
Расчет масок CIDR
CIDR (classless inter-domain routing, бесклассовая междоменная маршрутизация) — еще один часто применяемый способ компактного выражения маски подсети. Формат CIDR предусматривает использование косой черты (/), за которой указывается число разрядов в маске подсети. Например, маска подсети 255.255.252.0 эквивалентна выражению/22. На экране 3 показано, почему это так: в маске представлен набор из 22 разрядов (то есть сумма единиц, содержащихся во всех четырех октетах).
Экран 3. Набор разрядов в маске подсети |
К сожалению, класс IPAddress не располагает средствами для прямой конвертации формата CIDR («число разрядов») в формат маски подсети dotted-quad и обратно. Чтобы компенсировать этот недостаток, я написал функции PowerShell, представленные в листинге 2.
Функция ConvertTo-IPv4MaskString
Функция ConvertTo-IPv4MaskString рассчитывает значение маски как число с помощью следующей формулы:
((2n)-1) — shl (32-n)
Где n — это число разрядов. Поскольку в системе PowerShell version 2 оператор -shl не используется, в функции применяется другая формула:
((2n)-1) × (2(32-n))
Далее функция преобразует значение маски в число из четырех байтов с помощью класса BitConverter. Наконец, функция извлекает байты из значения в обратном порядке, разделяя их точками. Порядок байтов приходится менять на обратный потому, что при записи IPv4-адресов первым идет наиболее значимый байт (такой порядок именуют также сетевым или big endian). Но процессор сохраняет байты в другом порядке; первым идет наименее значимый байт (такой порядок именуется также порядком хоста или little endian).
Рассмотрим пример. Допустим, мы хотим найти строку маски подсети, соответствующую 26 разрядам. Расчет производится следующим образом:
(226-1) × (2(32-26)) = (0x4000000-1) × (26) = 0x03FFFFFF × 0x40 = 0xFFFFFFC0
Если представить шестнадцатеричные значения как двузначные пары, мы получим строку FF FF FF C0, или 255 255 255 192.
На экране 4 функция ConvertTo-IPv4MaskString представлена в действии. Первая команда указывает путь к функциям, предваряя его точкой и пробелом, из файла сценария CIDRFunctions.ps1, дабы обеспечить доступ к ним в ходе сеанса работы с PowerShell, а вторая команда перечисляет все строки маски подсети, от 8 до 29 разрядов.
Экран 4. Перечисление всех возможных значений маски подсети |
Функция Test-IPv4MaskString
Функция Test-IPv4MaskString определяет, является ли строка с десятичным представлением каждого байта и межбайтным разделителем в виде точки допустимым значением для маски подсети. Для решения этой задачи используется регулярное выражение. Функция возвращает значение $true, если строка соответствует допустимой строке маски подсети, или значение $false при отсутствии такого соответствия. Эта функция необходима для выполнения функции ConvertTo-IPv4MaskBits.
Функция ConvertTo-IPv4MaskBits
Для получения численного значения строки маски подсети эта функция создает объект IPAddress и считывает его свойство Address. Затем с помощью алгоритма Брайана Кернигана она подсчитывает число наборов разрядов в значении и возвращает число разрядов. К примеру, выполнив команду
ConvertTo-IPv4MaskBits "255.255.254.0"
мы получаем на выходе число 23.
Успешная работа с адресами IPv4
Реализовав класс IPAddress, разработчики PowerShell обле гчили работу пользователей с адресами IPv4. Кроме того, функции ConvertTo-IPv4MaskString и ConvertTo-IPv4MaskBits дают возможность преобразовывать маски подсети из формата CIDR в формат четырехбайтного адреса dotted-quad и обратно. Включите эти функции в свой набор инструментов, и вы сможете с легкостью решать любые задачи, связанные с обработкой адресов IPv4.
$params = @{ "ComputerName" = "". "Class" = "Win32_NetworkAdapterConfiguration" "Filter" = "IPEnabled=TRUE" } $netConfigs = Get-WMIObject @params foreach ( $netConfig in $netConfigs ) { for ( $i = 0; $i -lt $netConfig.IPAddress.Count; $i++ ) { $netConfig.IPAddress[$i] -match '(\d{1,3}\).{3}\d{1,3}' ) { $ipString = $netConfig.IPAddress[$i] $ip = [IPAddress] $ipString $maskString = $netConfig.IPSubnet[$i] $mask = [IPAddress] $maskString $netID = [IPAddress] ($ip.Address -band $mask.Address) "IP address: {0} " -f $ip.IPAddressToString "Subnet mask: {0}" -f $mask.IPAddressToString "Network ID: {0}" -f $netID.IPAddressToString } } }
function ConvertTo-IPv4MaskString { <# .РЕЗЮМЕ Преобразует число разрядов (0 - 32) в строку маски сети IPv4 (например, "255.255.255.0"). .ОПИСАНИЕ Преобразует число разрядов (0 - 32) в строку маски сети IPv4 (например, "255.255.255.0"). .ПАРАМЕТР MaskBits Указывает число разрядов в маске. #> param( [parameter(Mandatory=$true)] [ValidateRange(0,32)] [Int] $MaskBits ) $mask = ([Math]::Pow(2, $MaskBits) - 1) * [Math]::Pow(2, (32 - $MaskBits)) $bytes = [BitConverter]::GetBytes([UInt32] $mask) (($bytes.Count - 1).. 0 | ForEach-Object { [String] $bytes[$_] }) -join "". } function Test-IPv4MaskString { <# .РЕЗЮМЕ Определяет, действительна ли строка маски подсети IPv4 (например, "255.255.255.0"). .ОПИСАНИЕ Определяет, действительна ли строка маски подсети IPv4 (например, "255.255.255.0"). ПАРАМЕТР MaskString Указывает строку маски подсети (например, "255.255.255.0"). #> param( [parameter(Mandatory=$true)] [String] $MaskString ) $validBytes = '0|128|192|224|240|248|252|254|255' $maskPattern = ('^((({0})\.0\.0\.0)|' -f $validBytes) + ('(255\.({0})\.0\.0)|' -f $validBytes) + ('(255\.255\.({0})\.0)|' -f $validBytes) + ('(255\.255\.255\.({0})))$' -f $validBytes) $MaskString -match $maskPattern } function ConvertTo-IPv4MaskBits { <# .РЕЗЮМЕ Возвращает число разрядов (0-32) в строке маски сети (например, "255.255.255.0"). .ОПИСАНИЕ Возвращает число разрядов (0-32) в строке маски сети (например, "255.255.255.0"). .ПАРАМЕТР MaskString Указывает строку маски сети (например, "255.255.255.0"). #> param( [parameter(Mandatory=$true)] [ValidateScript({Test-IPv4MaskString $_})] [String] $MaskString ) $mask = ([IPAddress] $MaskString).Address for ( $bitCount = 0; $mask -ne 0; $bitCount++ ) { $mask = $mask -band ($mask - 1) } $bitCount }