С адресами 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.

 

Использование класса .NET IPAddress
Экран 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).

 

Расчет идентификатора сети с помощью оператора -band
Экран 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-IPv4­MaskBits дают возможность преобразовывать маски подсети из формата CIDR в формат четырехбайтного адреса dotted-quad и обратно. Включите эти функции в свой набор инструментов, и вы сможете с легкостью решать любые задачи, связанные с обработкой адресов IPv4.

Листинг 1. Расчет текущего сетевого идентификатора компьютера
$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
    }
  }
}
Листинг 2. IPv4CIDRFunctions.ps1
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
}